微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

在结构中封装IntPtr 会导致midiStream函数出错,但将数组展开为一堆字段

我正在尝试使用C#中的Windows多媒体MIDIfunction。 特别:

MMRESULT midioutPrepareHeader( HMIdioUT hmo,LPMIDIHDR lpMidioutHdr,UINT cbMidioutHdr ); MMRESULT midioutUnprepareHeader( HMIdioUT hmo,UINT cbMidioutHdr ); MMRESULT midistreamOut( HMIdisTRM hMidistream,LPMIDIHDR lpMidiHdr,UINT cbMidiHdr ); MMRESULT midistreamRestart( HMIdisTRM hms ); /* MIDI data block header */ typedef struct midihdr_tag { LPSTR lpData; /* pointer to locked data block */ DWORD dwBufferLength; /* length of data in data block */ DWORD dwBytesRecorded; /* used for input only */ DWORD_PTR dwUser; /* for client's use */ DWORD dwFlags; /* assorted flags (see defines) */ struct midihdr_tag far *lpNext; /* reserved for driver */ DWORD_PTR reserved; /* reserved for driver */ #if (WINVER >= 0x0400) DWORD dwOffset; /* Callback offset into buffer */ DWORD_PTR dwReserved[8]; /* Reserved for MMSYstem */ #endif } MIDIHDR,*PMIDIHDR,NEAR *NPMIDIHDR,FAR *LPMIDIHDR;

一个C程序,我可以成功地使用这些function,通过执行以下操作:

HMIdisTRM hms; midistreamOpen(&hms,...); MIDIHDR hdr; hdr.this = that; ... midistreamRestart(hms); midioutPrepareHeader(hms,&hdr,sizeof(MIDIHDR)); // sizeof(MIDIHDR) == 64 midistreamOut(hms,sizeof(MIDIHDR)); // wait for an event that is set from the midi callback when the playback has finished WaitForSingleObject(...); midioutUnprepareHeader(hms,sizeof(MIDIHDR));

上述调用序列的工作原理并没有产生任何错误(为了可读性,错误检查已被省略)。

为了在C#中使用它们,我创build了一些P / Invoke代码

导入DLL到Python 3没有imp.load_dynamic

如何杀死Windows 64位平台上的python 2.5进程?

在.NET中是否有一个文件扩展名分隔符字符常量?

在Windows上debuggingssl握手的选项

在运行时,如何判断我是否在WinXP +上? 的win32

[DllImport("winmm.dll")] public static extern int midioutPrepareHeader(IntPtr handle,ref MidiHeader header,uint headerSize); [DllImport("winmm.dll")] public static extern int midioutUnprepareHeader(IntPtr handle,uint headerSize); [DllImport("winmm.dll")] public static extern int midistreamOut(IntPtr handle,uint headerSize); [DllImport("winmm.dll")] public static extern int midistreamRestart(IntPtr handle); [StructLayout(LayoutKind.Sequential)] public struct MidiHeader { public IntPtr Data; public uint BufferLength; public uint BytesRecorded; public IntPtr UserData; public uint Flags; public IntPtr Next; public IntPtr Reserved; public uint Offset; //[MarshalAs(UnmanagedType.ByValArray,SizeConst = 8)] //public IntPtr[] Reserved2; public IntPtr Reserved0; public IntPtr Reserved1; public IntPtr Reserved2; public IntPtr Reserved3; public IntPtr Reserved4; public IntPtr Reserved5; public IntPtr Reserved6; public IntPtr Reserved7; }

通话顺序与C:

var hdr = new MidiHeader(); hdr.this = that; midistreamRestart(handle); midioutPrepareHeader(handle,ref header,headerSize); // headerSize == 64 midistreamOut(handle,headerSize); mre.WaitOne(); // wait until the midi playback has finished. midioutUnprepareHeader(handle,headerSize);

MIDI输出工作,代码不会产生错误错误检查再次被省略)。

只要我用MidiHeader的数组取消注释这两行,而取消Reserved0到Reserved7字段,它不再工作。 会发生什么是以下几点:

一切正常,直到包括midistreamOut在内。 我可以听到MIDI的输出。 播放长度是正确的。 但是,回放结束时从不调用事件callback。

此时, MidiHeader.Flags值为0xe ,表示该stream仍在播放(即使回放已通过回放已完成的消息通知)。 MidiHeader.Flags的值应该是9 ,表示stream已经播放完毕。

对midioutUnprepareHeader的调用失败,错误代码为0x41 (“媒体数据仍在播放时无法执行此操作,重置设备或等待数据播放完毕”)。 请注意,根据错误消息中的build议重置设备实际上并没有解决问题(也没有多次等待或尝试)。

正确工作的另一个变种是,如果我使用IntPtr而不是在C#声明的签名中ref MidiHeader ,然后手动分配非托pipe内存,将我的MidiHeader结构复制到该内存,然后使用分配内存调用函数

此外,我试图减less我传递给headerSize参数为32的大小。由于字段是保留的(事实上,在以前版本的Windows API中不存在),它们似乎没有任何特定的用途。 但是,这并不能解决问题,即使Windows甚至不应该知道该数组存在,因此它不应该做任何事情。 解决问题(即,数组以及8个Reserved*字段被注释掉,并且headerSize为32)。

这向我暗示, IntPtr[] Reserved2不能被正确编组,并试图这样做是破坏其他值。 为了validation,我创build了一个平台调用testing项目:

WIN32PROJECT1_API void __stdcall test_function(struct test_struct_t *s) { printf("%u %u %u %u %u %u %u %un",s->test0,s->test1,s->test2,s->test3,s->test4,s->test5,s->test6,s->test7); for (int i = 0; i < sizeof(s->pointer_array) / sizeof(s->pointer_array[0]); ++i) { printf("%u ",((uint32_t)s->pointer_array[i]) >> 16); } printf("n"); } typedef int32_t *test_ptr; struct test_struct_t { test_ptr test0; uint32_t test1; uint32_t test2; test_ptr test3; uint32_t test4; test_ptr test5; uint32_t test6; uint32_t test7; test_ptr pointer_array[8]; };

从C#调用的是:

[StructLayout(LayoutKind.Sequential)] struct TestStruct { public IntPtr test0; public uint test1; public uint test2; public IntPtr test3; public uint test4; public IntPtr test5; public uint test6; public uint test7; [MarshalAs(UnmanagedType.ByValArray,SizeConst = 8)] public IntPtr[] pointer_array; } [DllImport("Win32Project1.dll")] static extern void test_function(ref TestStruct s); static void Main(string[] args) { TestStruct s = new TestStruct(); s.test0 = IntPtr.Zero; s.test1 = 1; s.test2 = 2; s.test3 = IntPtr.Add(IntPtr.Zero,3); s.test4 = 4; s.test5 = IntPtr.Add(IntPtr.Zero,5); s.test6 = 6; s.test7 = 7; s.pointer_array = new IntPtr[8]; for (int i = 0; i < s.pointer_array.Length; ++i) { s.pointer_array[i] = IntPtr.Add(IntPtr.Zero,i << 16); } test_function(ref s); Console.ReadLine(); }

并且输出如预期的那样,因此IntPtr[] pointer_array的封送处理在这个程序中起作用。

我知道不使用SafeHandle是不太理想的,但是,当使用这个函数的时候,使用这个数组的MIDI函数的行为更加怪异,所以我select了一次解决一个问题。

为什么使用IntPtr[] Reserved2会导致错误

这里有更多的代码生成一个完整的例子:

C代码

/* * example9.c * * Created on: Dec 21,2011 * Author: David J. Rager * Email: djrager@fourthwoods.com * * This code is hereby released into the public domain per the Creative Commons * Public Domain dedication. * * http://http://creativecommons.org/publicdomain/zero/1.0/ */ #include <windows.h> #include <mmsystem.h> #include <stdio.h> HANDLE event; static void CALLBACK example9_callback(HMIdioUT out,UINT msg,DWORD dwInstance,DWORD dwParam1,DWORD dwParam2) { switch (msg) { case MOM_DONE: SetEvent(event); break; case MOM_POSITIONCB: case MOM_OPEN: case MOM_CLOSE: break; } } int main() { unsigned int streambufsize = 24; char* streambuf = NULL; HMIdisTRM out; MIDIPROPTIMEDIV prop; MIDIHDR mhdr; unsigned int device = 0; streambuf = (char*)malloc(streambufsize); if (streambuf == NULL) goto error2; memset(streambuf,streambufsize); if ((event = CreateEvent(0,FALSE,0)) == NULL) goto error3; memset(&mhdr,sizeof(mhdr)); mhdr.lpData = streambuf; mhdr.dwBufferLength = mhdr.dwBytesRecorded = streambufsize; mhdr.dwFlags = 0; // flags and event code mhdr.lpData[8] = (char)0x90; mhdr.lpData[9] = 63; mhdr.lpData[10] = 0x55; mhdr.lpData[11] = 0; // next event mhdr.lpData[12] = 96; // delta time? mhdr.lpData[20] = (char)0x80; mhdr.lpData[21] = 63; mhdr.lpData[22] = 0x55; mhdr.lpData[23] = 0; if (midistreamOpen(&out,&device,1,(DWORD)example9_callback,CALLBACK_FUNCTION) != MMSYSERR_NOERROR) goto error4; //printf("sizeof midiheader = %dn",sizeof(MIDIHDR)); if (midioutPrepareHeader((HMIdioUT)out,&mhdr,sizeof(MIDIHDR)) != MMSYSERR_NOERROR) goto error5; if (midistreamRestart(out) != MMSYSERR_NOERROR) goto error6; if (midistreamOut(out,sizeof(MIDIHDR)) != MMSYSERR_NOERROR) goto error7; WaitForSingleObject(event,INFINITE); error7: //midioutReset((HMIdioUT)out); error6: MMRESULT blah = midioutUnprepareHeader((HMIdioUT)out,sizeof(MIDIHDR)); printf("stuff: %dn",blah); error5: midistreamClose(out); error4: CloseHandle(event); error3: free(streambuf); error2: //free(tracks); error1: //free(hdr); return(0); }

C#代码

using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading; namespace MidioutTest { class Program { [DllImport("winmm.dll")] public static extern int midistreamOpen(out IntPtr handle,ref uint deviceid,uint cMidi,MidiCallback callback,IntPtr userData,uint flags); [DllImport("winmm.dll")] public static extern int midistreamOut(IntPtr handle,uint headerSize); [DllImport("winmm.dll")] public static extern int midistreamRestart(IntPtr handle); [DllImport("winmm.dll")] public static extern int midioutPrepareHeader(IntPtr handle,uint headerSize); [DllImport("winmm.dll",CharSet = CharSet.Unicode)] public static extern int midioutGetErrorText(int mmsyserr,StringBuilder errMsg,int capacity); [DllImport("winmm.dll")] public static extern int midistreamClose(IntPtr handle); public delegate void MidiCallback(IntPtr handle,uint msg,IntPtr instance,IntPtr param1,IntPtr param2); private static readonly ManualResetEvent mre = new ManualResetEvent(false); private static void TestMidiCallback(IntPtr handle,IntPtr param2) { Debug.WriteLine(msg.ToString()); if (msg == MOM_DONE) { Debug.WriteLine("MOM_DONE"); mre.Set(); } } public const uint MOM_DONE = 0x3C9; public const int MMSYSERR_NOERROR = 0; public const int MAXERRORLENGTH = 256; public const uint CALLBACK_FUNCTION = 0x30000; public const uint MidiHeaderSize = 64; public static void CheckMidioutMmsyserr(int mmsyserr) { if (mmsyserr != MMSYSERR_NOERROR) { var sb = new StringBuilder(MAXERRORLENGTH); var errorResult = midioutGetErrorText(mmsyserr,sb,sb.Capacity); if (errorResult != MMSYSERR_NOERROR) { throw new /*Midi*/Exception("An error occurred and there was another error while attempting to retrieve the error message."/*,mmsyserr*/); } throw new /*Midi*/Exception(sb.ToString()/*,mmsyserr*/); } } static void Main(string[] args) { IntPtr handle; uint deviceid = 0; CheckMidioutMmsyserr(midistreamOpen(out handle,ref deviceid,TestMidiCallback,IntPtr.Zero,CALLBACK_FUNCTION)); try { var bytes = new byte[24]; IntPtr buffer = Marshal.AllocHGlobal(bytes.Length); try { MidiHeader header = new MidiHeader(); header.Data = buffer; header.BufferLength = 24; header.BytesRecorded = 24; header.UserData = IntPtr.Zero; header.Flags = 0; header.Next = IntPtr.Zero; header.Reserved = IntPtr.Zero; header.Offset = 0; #warning uncomment if using array //header.Reserved2 = new IntPtr[8]; // flags and event code bytes[8] = 0x90; bytes[9] = 63; bytes[10] = 0x55; bytes[11] = 0; // next event bytes[12] = 96; bytes[20] = 0x80; bytes[21] = 63; bytes[22] = 0x55; bytes[23] = 0; Marshal.copy(bytes,buffer,bytes.Length); CheckMidioutMmsyserr(midistreamRestart(handle)); CheckMidioutMmsyserr(midioutPrepareHeader(handle,MidiHeaderSize)); CheckMidioutMmsyserr(midistreamOut(handle,MidiHeaderSize)); mre.WaitOne(); CheckMidioutMmsyserr(midioutUnprepareHeader(handle,MidiHeaderSize)); } finally { Marshal.FreeHGlobal(buffer); } } finally { midistreamClose(handle); } } } [StructLayout(LayoutKind.Sequential)] public struct MidiHeader { public IntPtr Data; public uint BufferLength; public uint BytesRecorded; public IntPtr UserData; public uint Flags; public IntPtr Next; public IntPtr Reserved; public uint Offset; #if false [MarshalAs(UnmanagedType.ByValArray,SizeConst = 8)] public IntPtr[] Reserved2; #else public IntPtr Reserved0; public IntPtr Reserved1; public IntPtr Reserved2; public IntPtr Reserved3; public IntPtr Reserved4; public IntPtr Reserved5; public IntPtr Reserved6; public IntPtr Reserved7; #endif } }

使用数据绑定从UI更改数据

在linux上使用python编写DOS行结尾的文本文件

基于Web的安装程序和可执行文件安装程序在Windows 3上的Python 3有什么区别?

如何find使用C语言的桌面path?

therubyracer安装在libv8安装的windows –with-system-v8

从midioutPrepareHeader的文档:

在将MIDI数据块传递给设备驱动程序之前,必须将缓冲区传递给midioutPrepareHeader函数来准备缓冲区。 标题准备好之后,不要修改缓冲区。 驱动程序完成使用缓冲区后,调用midioutUnprepareHeader函数

你不遵守这个。 编组器创建一个临时的本地版本的结构,它在调用midioutPrepareHeader 。 一旦midioutPrepareHeader返回,临时本地结构被销毁。 但是,MIDI代码仍然有一个参考。 这是关键点,MIDI代码持有对您的结构的引用,并需要能够访问它。

与单独的书面领域的版本工作,因为该结构是blittable。 因此,p / invoke编组人员通过固定与本地结构二进制兼容的托管结构来优化调用。 在调用midioutUnprepareHeader之前,GC仍然有机会重新定位该结构,但似乎还没有被抓住。 如果你坚持可调整的结构,你将需要钉住它,直到你调用midioutUnprepareHeader 。

所以,底线是你需要提供一个结构,直到你调用midioutUnprepareHeader 。 我个人建议你使用Marshal.AllocHGlobal , Marshal.StructuretoPtr ,然后Marshal.FreeHGlobal一旦midioutUnprepareHeader返回。 显然,将参数从ref MidiHeader到IntPtr 。

我不认为我需要向您展示任何代码,因为从您的问题中可以清楚地知道如何做这些事情。 事实上,我提出的解决方案就是你已经试过并观察过的工作。 但是现在你知道为什么了!

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐