2

マネージ 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
+ 予約済み 0x7c07c9a0 System.IntPtr

これは明らかに、私が合格することを期待していたものではありません。ビュー内のフィールドの順序が明らかに気になります。「ローカル」ビューでレコードを表示するときに、Visual Studio .NET が実際のメモリの順序を気にするかどうかはわかりませんが、構造体で指定した順序で表示されないことは明らかです。

次に、データ ポインターがなく、bufferLength フィールドが高すぎます。興味深いことに、bytesRecorded フィールドは正確に 64000 です。bufferLength と bytesRecorded は両方とも 64000 になると思います。何がうまくいかないのか正確にはわかりません。誰かがこれについて私を助けてくれるかもしれません。私はマネージ コードのプログラミングとマーシャリングにまったく慣れていないので、私が行ったすべてのばかげたことについて、あまり厳しくしないでください。

ああ、ここで見つけた WAVEHDR の C コード定義は次のとおりです。C# 構造体定義で何か間違ったことをした可能性があると思います。

/* 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# からのアンマネージ コードの呼び出しなどに関する良いリンクがあれば、本当に感謝しています。私は何を間違っていますか?期待したものが得られないのはなぜですか。

4

3 に答える 3

2

あなたの P/Invoke 宣言は完璧です。ただし、投稿した WAVEHDR ダンプには非常に奇妙な点があります。lpData フィールドがありません。これを挿入すると、すべての数値が適切に整列します (つまり、lpData = 0x19904c00、dwBufferLength = 0x0000fa00 など)。

これがどのように発生したのかはわかりませんが、どういうわけか間違った WAVEHDR 宣言を使用しています。WinMM.WinMM がヒントになるはずです。

于 2010-04-15T11:51:11.787 に答える
1

PInvoke.Net は、PInvoke 宣言を見つけるための場所です。 このページでは、waveInAddBuffer メソッドとそれに相当する C# メソッド、および WAVEHDR へのリンクについて説明します。

あなたが使用しているさまざまな方法を調べましたが、あなたの場合に役立つものは何も見つかりませんでした. PInvoke.net のバージョンとあなたのバージョンとの違いは、PInvoke が StructLayout の CharSet プロパティを使用することですが、それは関係ないと思います。

相互運用性に関する優れた書籍は、NET-and-COMです。

于 2010-04-15T12:12:21.350 に答える
1

わかりました、私はそれを理解しました。私のコードはすべて基本的に正しかった。しかし、私は 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();
            }
    }

ご協力ありがとうございました。誰かが私が思いついたコードを使用できることを願っています。

于 2010-04-16T19:20:33.437 に答える