C# 在C中编组到本机库#
在托管C#代码中调用本机库函数时遇到问题。我正在为3.5紧凑型框架(WindowsMobile6.x)开发,以防这会带来任何不同 我正在使用coredll.dll中的waveIn*函数(我相信这些函数是在普通窗口中的winmm.dll中)。这就是我想到的:C# 在C中编组到本机库#,c#,unmanaged,marshalling,native,winmm,C#,Unmanaged,Marshalling,Native,Winmm,在托管C#代码中调用本机库函数时遇到问题。我正在为3.5紧凑型框架(WindowsMobile6.x)开发,以防这会带来任何不同 我正在使用coredll.dll中的waveIn*函数(我相信这些函数是在普通窗口中的winmm.dll中)。这就是我想到的: // namespace winmm; class winmm [StructLayout(LayoutKind.Sequential)] public struct WAVEFORMAT { public ushort wForma
// namespace winmm; class winmm
[StructLayout(LayoutKind.Sequential)]
public struct WAVEFORMAT
{
public ushort wFormatTag;
public ushort nChannels;
public uint nSamplesPerSec;
public uint nAvgBytesPerSec;
public ushort nBlockAlign;
public ushort wBitsPerSample;
public ushort cbSize;
}
[StructLayout(LayoutKind.Sequential)]
public struct WAVEHDR
{
public IntPtr lpData;
public uint dwBufferLength;
public uint dwBytesRecorded;
public IntPtr dwUser;
public uint dwFlags;
public uint dwLoops;
public IntPtr lpNext;
public IntPtr reserved;
}
public delegate void AudioRecordingDelegate(IntPtr deviceHandle, uint message, IntPtr instance, ref WAVEHDR wavehdr, IntPtr reserved2);
[DllImport("coredll.dll")]
public static extern int waveInAddBuffer(IntPtr hWaveIn, ref WAVEHDR lpWaveHdr, uint cWaveHdrSize);
[DllImport("coredll.dll")]
public static extern int waveInPrepareHeader(IntPtr hWaveIn, ref WAVEHDR lpWaveHdr, uint Size);
[DllImport("coredll.dll")]
public static extern int waveInStart(IntPtr hWaveIn);
// some other class
private WinMM.WinMM.AudioRecordingDelegate waveIn;
private IntPtr handle;
private uint bufferLength;
private void setupBuffer()
{
byte[] buffer = new byte[bufferLength];
GCHandle bufferPin = GCHandle.Alloc(buffer, GCHandleType.Pinned);
WinMM.WinMM.WAVEHDR hdr = new WinMM.WinMM.WAVEHDR();
hdr.lpData = bufferPin.AddrOfPinnedObject();
hdr.dwBufferLength = this.bufferLength;
hdr.dwFlags = 0;
int i = WinMM.WinMM.waveInPrepareHeader(this.handle, ref hdr, Convert.ToUInt32(Marshal.SizeOf(hdr)));
if (i != WinMM.WinMM.MMSYSERR_NOERROR)
{
this.Text = "Error: waveInPrepare";
return;
}
i = WinMM.WinMM.waveInAddBuffer(this.handle, ref hdr, Convert.ToUInt32(Marshal.SizeOf(hdr)));
if (i != WinMM.WinMM.MMSYSERR_NOERROR)
{
this.Text = "Error: waveInAddrBuffer";
return;
}
}
private void setupWaveIn()
{
WinMM.WinMM.WAVEFORMAT format = new WinMM.WinMM.WAVEFORMAT();
format.wFormatTag = WinMM.WinMM.WAVE_FORMAT_PCM;
format.nChannels = 1;
format.nSamplesPerSec = 8000;
format.wBitsPerSample = 8;
format.nBlockAlign = Convert.ToUInt16(format.nChannels * format.wBitsPerSample);
format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
this.bufferLength = format.nAvgBytesPerSec;
format.cbSize = 0;
int i = WinMM.WinMM.waveInOpen(out this.handle, WinMM.WinMM.WAVE_MAPPER, ref format, Marshal.GetFunctionPointerForDelegate(waveIn), 0, WinMM.WinMM.CALLBACK_FUNCTION);
if (i != WinMM.WinMM.MMSYSERR_NOERROR)
{
this.Text = "Error: waveInOpen";
return;
}
setupBuffer();
WinMM.WinMM.waveInStart(this.handle);
}
在过去的几天里,我读了很多关于编组的文章,但是我没有让这段代码正常工作。当缓冲区已满时调用回调函数(waveIn)时,wavehdr中传回的hdr结构显然已损坏。以下是结构在该点上的外观示例:
- wavehdr {WinMM.WinMM.WAVEHDR} WinMM.WinMM.WAVEHDR
dwBufferLength 0x19904c00 uint
dwBytesRecorded 0x0000fa00 uint
dwFlags 0x00000003 uint
dwLoops 0x1990f6a4 uint
+ dwUser 0x00000000 System.IntPtr
+ lpData 0x00000000 System.IntPtr
+ lpNext 0x00000000 System.IntPtr
+ reserved 0x7c07c9a0 System.IntPtr
如果你习惯于使用所有低级工具,如指针运算、强制转换等,那么开始编写托管代码是件很痛苦的事。这就像是双手绑在背上,试图学习如何游泳一样。
我尝试过的一些事情(没有效果):
.NET compact framework似乎不支持[StructLayout]中的Pack=2^x指令。
我尝试了[StructLayout(LayoutKind.Explicit)],并使用了4字节和8字节对齐。4字节对齐给了我与上述代码相同的结果,而8字节对齐只会让事情变得更糟——但这正是我所期望的。
有趣的是,如果我将代码从setupBuffer移动到setupWaveIn中,并且没有在类的上下文中声明GCHandle,但是在setupWaveIn的本地上下文中,回调函数返回的结构中似乎没有损坏。然而,我不确定为什么会出现这种情况,以及如何利用这些知识来修复代码。算了吧。我把东西和我用过的旧代码混在一起了
我非常感谢关于编组、从C#调用非托管代码等方面的好链接。如果有人能指出我的错误,我将非常高兴。我做错了什么?为什么我得不到我所期望的 您的p/Invoke声明是无可挑剔的。但是,您发布的WAVEHDR转储有一些非常奇怪的地方。它缺少lpData字段。如果插入该选项,则所有数字将正确对齐(即lpData=0x19904c00、dwBufferLength=0x0000fa00等)
不确定这是怎么发生的,但不知何故它使用了错误的WAVEHDR声明。WinMM.WinMM应该是一个提示。PInvoke.Net是查找PInvoke声明的地方。 介绍waveInAddBuffer方法及其C#等价物,以及与WAVEHDR的链接 我查看了您使用的各种方法,但在您的案例中找不到任何有用的方法。与PInvoke.net版本和您的版本不同的是,PInvoke使用StructLayout的字符集属性,但我猜这与此无关
关于互操作性主题的一本好书是:好的,我知道了。我所有的代码基本上都是正确的。然而,我搞砸了WAVEHDR结构。waveIn*函数不仅期望对WAVEHDR结构的引用,而且还期望该结构持续到调用waveInHeaderUnprepare为止。因此,WAVEHDR结构需要在全局或至少足够大的上下文中创建,以支持对其调用waveInHeaderUnprepare,并且可能还需要使用GCHandle固定,以便它不会更改其在内存中的位置(在托管代码中不能保证这一点)。 这里是我的更新和清理代码:
private WinMM.WinMM.AudioRecordingDelegate waveIn;
private IntPtr handle;
private WinMM.WinMM.WAVEHDR header;
private GCHandle headerPin;
private GCHandle bufferPin;
private byte[] buffer;
private uint bufferLength;
private void setupBuffer()
{
header.lpData = bufferPin.AddrOfPinnedObject();
header.dwBufferLength = bufferLength;
header.dwFlags = 0;
header.dwBytesRecorded = 0;
header.dwLoops = 0;
header.dwUser = IntPtr.Zero;
header.lpNext = IntPtr.Zero;
header.reserved = IntPtr.Zero;
int i = WinMM.WinMM.waveInPrepareHeader(handle, ref header, Convert.ToUInt32(Marshal.SizeOf(header)));
if (i != WinMM.WinMM.MMSYSERR_NOERROR)
{
this.Text = "Error: waveInPrepare " + i.ToString();
return;
}
i = WinMM.WinMM.waveInAddBuffer(handle, ref header, Convert.ToUInt32(Marshal.SizeOf(header)));
if (i != WinMM.WinMM.MMSYSERR_NOERROR)
{
this.Text = "Error: waveInAddrBuffer";
return;
}
}
private void setupWaveIn()
{
handle = new IntPtr();
WinMM.WinMM.WAVEFORMAT format;
format.wFormatTag = WinMM.WinMM.WAVE_FORMAT_PCM;
format.nChannels = 1;
format.nSamplesPerSec = 8000;
format.wBitsPerSample = 8;
format.nBlockAlign = Convert.ToUInt16(format.nChannels * format.wBitsPerSample);
format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
bufferLength = format.nAvgBytesPerSec / 800;
headerPin = GCHandle.Alloc(header, GCHandleType.Pinned);
buffer = new byte[bufferLength];
bufferPin = GCHandle.Alloc(buffer, GCHandleType.Pinned);
format.cbSize = 0;
int i = WinMM.WinMM.waveInOpen(out handle, WinMM.WinMM.WAVE_MAPPER, ref format, Marshal.GetFunctionPointerForDelegate(waveIn), IntPtr.Zero, WinMM.WinMM.CALLBACK_FUNCTION);
if (i != WinMM.WinMM.MMSYSERR_NOERROR)
{
this.Text = "Error: waveInOpen";
return;
}
setupBuffer();
WinMM.WinMM.waveInStart(handle);
}
private void callbackWaveIn(IntPtr deviceHandle, uint message, IntPtr instance, ref WinMM.WinMM.WAVEHDR wavehdr, IntPtr reserved2)
{
if (message == WinMM.WinMM.WIM_DATA)
if (this.InvokeRequired)
this.Invoke(waveIn, deviceHandle, message, instance, wavehdr, reserved2);
else
{
if (wavehdr.dwBytesRecorded > 0)
{
foreach (byte buf in buffer)
{
// do something cool with your byte stream
}
}
int i = WinMM.WinMM.waveInUnprepareHeader(deviceHandle, ref header, Convert.ToUInt32(Marshal.SizeOf(wavehdr)));
if (i != WinMM.WinMM.MMSYSERR_NOERROR)
{
this.Text = "Error: waveInUnprepareHeader " + i;
}
setupBuffer();
}
}
谢谢你的帮助。我希望有人能使用我能想到的代码。它没有遗漏,但在其他IntPtr对象的末尾。我发现Being非常奇怪,但我不确定是否可以通过字段在VS.net本地视图中的位置来判断它的内存位置。是的,非常奇怪。调试器错误?当您读取dwBufferLength字段时,是否确实收到垃圾?是的,收到了。我更改了一些内容,并将byte[]buffer和GCHandle bufferPin变量移动到类范围内,因为我担心其中一个(或两个)可能会在某个时候被垃圾收集器破坏,从而导致API函数失效。因为我改变了wavehdr.dwBufferLength不再是一个任意大的数字,而是0。虽然它仍然不起作用,我相信这是一个进步。谢谢你的链接,但遗憾的是,正如你已经说过的,他们没有帮助我在这个问题上。我试着玩弄CharSet属性,但没有成功。
private WinMM.WinMM.AudioRecordingDelegate waveIn;
private IntPtr handle;
private WinMM.WinMM.WAVEHDR header;
private GCHandle headerPin;
private GCHandle bufferPin;
private byte[] buffer;
private uint bufferLength;
private void setupBuffer()
{
header.lpData = bufferPin.AddrOfPinnedObject();
header.dwBufferLength = bufferLength;
header.dwFlags = 0;
header.dwBytesRecorded = 0;
header.dwLoops = 0;
header.dwUser = IntPtr.Zero;
header.lpNext = IntPtr.Zero;
header.reserved = IntPtr.Zero;
int i = WinMM.WinMM.waveInPrepareHeader(handle, ref header, Convert.ToUInt32(Marshal.SizeOf(header)));
if (i != WinMM.WinMM.MMSYSERR_NOERROR)
{
this.Text = "Error: waveInPrepare " + i.ToString();
return;
}
i = WinMM.WinMM.waveInAddBuffer(handle, ref header, Convert.ToUInt32(Marshal.SizeOf(header)));
if (i != WinMM.WinMM.MMSYSERR_NOERROR)
{
this.Text = "Error: waveInAddrBuffer";
return;
}
}
private void setupWaveIn()
{
handle = new IntPtr();
WinMM.WinMM.WAVEFORMAT format;
format.wFormatTag = WinMM.WinMM.WAVE_FORMAT_PCM;
format.nChannels = 1;
format.nSamplesPerSec = 8000;
format.wBitsPerSample = 8;
format.nBlockAlign = Convert.ToUInt16(format.nChannels * format.wBitsPerSample);
format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
bufferLength = format.nAvgBytesPerSec / 800;
headerPin = GCHandle.Alloc(header, GCHandleType.Pinned);
buffer = new byte[bufferLength];
bufferPin = GCHandle.Alloc(buffer, GCHandleType.Pinned);
format.cbSize = 0;
int i = WinMM.WinMM.waveInOpen(out handle, WinMM.WinMM.WAVE_MAPPER, ref format, Marshal.GetFunctionPointerForDelegate(waveIn), IntPtr.Zero, WinMM.WinMM.CALLBACK_FUNCTION);
if (i != WinMM.WinMM.MMSYSERR_NOERROR)
{
this.Text = "Error: waveInOpen";
return;
}
setupBuffer();
WinMM.WinMM.waveInStart(handle);
}
private void callbackWaveIn(IntPtr deviceHandle, uint message, IntPtr instance, ref WinMM.WinMM.WAVEHDR wavehdr, IntPtr reserved2)
{
if (message == WinMM.WinMM.WIM_DATA)
if (this.InvokeRequired)
this.Invoke(waveIn, deviceHandle, message, instance, wavehdr, reserved2);
else
{
if (wavehdr.dwBytesRecorded > 0)
{
foreach (byte buf in buffer)
{
// do something cool with your byte stream
}
}
int i = WinMM.WinMM.waveInUnprepareHeader(deviceHandle, ref header, Convert.ToUInt32(Marshal.SizeOf(wavehdr)));
if (i != WinMM.WinMM.MMSYSERR_NOERROR)
{
this.Text = "Error: waveInUnprepareHeader " + i;
}
setupBuffer();
}
}