在C#中编组到本机库

时间:2010-04-15 10:09:17

标签: c# unmanaged marshalling native winmm

我无法从托管C#代码中调用本机库的函数。我正在为3.5紧凑框架(Windows Mobile 6.x)开发,以防万一这会产生任何不同。

我正在使用coredll.dll中的waveIn *函数(我相信常规Windows中的winmm.dll)。这就是我想出的:

// 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

这绝对不是我期望通过的。我显然关注视图中字段的顺序。我不知道Visual Studio .NET在“本地”视图中显示记录时是否关心实际的内存顺序,但它们显然不按我在结构中所规定的顺序显示。

然后没有数据指针,bufferLength字段也很高。有趣的是,bytesRecorded字段正好是64000 - bufferLength和bytesRecorded我希望它们都是64000。我不知道到底出了什么问题,也许有人可以帮我解决这个问题。我是托管代码编程和编组的绝对菜鸟,所以请不要因为我所做的所有愚蠢的事情而过于苛刻。

哦,这是WAVEHDR的C代码定义,我发现here,我相信我可能在C#struct定义中做错了什么:

/* wave data block header */
typedef struct wavehdr_tag {
    LPSTR       lpData;                 /* pointer to locked data buffer */
    DWORD       dwBufferLength;         /* length of data buffer */
    DWORD       dwBytesRecorded;        /* used for input only */
    DWORD_PTR   dwUser;                 /* for client's use */
    DWORD       dwFlags;                /* assorted flags (see defines) */
    DWORD       dwLoops;                /* loop control counter */
    struct wavehdr_tag FAR *lpNext;     /* reserved for driver */
    DWORD_PTR   reserved;               /* reserved for driver */
} WAVEHDR, *PWAVEHDR, NEAR *NPWAVEHDR, FAR *LPWAVEHDR;

如果您习惯使用所有那些低级工具,如指针算术,强制转换等,开始编写托管代码是一件痛苦的事。这就像试图学习如何用双手绑在背上游泳。 我试过的一些事情(没有效果): .NET紧凑框架似乎不支持[StructLayout]中的Pack = 2 ^ x指令。 我尝试了[StructLayout(LayoutKind.Explicit)]并使用了4个字节和8个字节的对齐方式。 4字节对齐给我与上面的代码相同的结果和8字节对齐只会让事情变得更糟 - 但这就是我所期望的。 有趣的是,如果我将setupBuffer中的代码移动到setupWaveIn中,并且不在类的上下文中声明GCHandle,但是在setupWaveIn的本地上下文中,回调函数返回的结构似乎没有被破坏。我不确定为什么会出现这种情况以及我如何使用这些知识来修复我的代码。忘了这一点。我把这些东西与我用过的旧代码混在一起。

我非常感谢编组上的任何良好链接,从C#调用非托管代码等等。如果有人能指出我的错误,我会很高兴。我究竟做错了什么?为什么我得不到我所期望的。

3 个答案:

答案 0 :(得分:2)

您的P / Invoke声明无可挑剔。但是,您发布的WAVEHDR转储有一些非常奇怪的东西。它缺少lpData字段。如果你插入它,所有数字都正确排列(即lpData = 0x19904c00,dwBufferLength = 0x0000fa00等)。

不确定这是怎么发生的,但它在某种程度上使用了错误的WAVEHDR声明。 WinMM.WinMM应该是一个提示。

答案 1 :(得分:1)

PInvoke.Net是寻找PInvoke声明的地方。 This page描述了waveInAddBuffer方法及其C#等价物,以及指向WAVEHDR的链接。

我查看了您使用的各种方法,但在您的情况下找不到任何有用的方法。与PInvoke.net的版本和你的版本的不同之处在于PInvoke使用了StructLayout的CharSet属性,但我想这是不相关的。

关于互操作性主题的好书是:NET-and-COM

答案 2 :(得分:0)

好的,我明白了。我的所有代码基本都是正确的。但是,我搞砸了WAVEHDR结构。 waveIn *函数不仅期望引用WAVEHDR结构,而且还希望这个结构能够持续到waveInHeaderUnprepare被调用为止。所以WAVEHDR结构需要在全局或至少足够大的上下文中创建以维持unl waveInHeaderUnprepare在其上调用并且可能需要使用GCHandle固定,以便它不会改变它在内存中的位置(即afaik)在托管代码中无法保证)。 这是我的更新和清理代码:

    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();
            }
    }

感谢您的帮助。我希望有人可以使用我能够提出的代码。