Маршаллинг в родную библиотеку в C # - PullRequest
1 голос
/ 15 апреля 2010

У меня проблемы с вызовом функций собственной библиотеки из управляемого кода C #. Я занимаюсь разработкой компактного фреймворка 3.5 (Windows Mobile 6.x) на всякий случай, если это что-то изменит.

Я работаю с функциями waveIn * из coredll.dll (я полагаю, они находятся в winmm.dll в обычной Windows). Вот что я придумал:

// 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), когда буфер заполнен, структура hdr, переданная обратно в wavehdr, очевидно, повреждена. Вот пример того, как структура выглядит в этой точке:

-      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. Я не знаю, что именно не так, может быть, кто-то может мне помочь в этом. Я абсолютный новичок в программировании управляемого кода и маршаллинге, поэтому, пожалуйста, не будьте слишком резкими со мной из-за всех глупостей, которые я, вероятно, сделал.

О, вот определение кода C для WAVEHDR, которое я нашел здесь , я полагаю, что, возможно, что-то не так в определении структуры 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 не поддерживает директиву Pack = 2 ^ x в [StructLayout]. Я попытался [StructLayout (LayoutKind.Explicit)] и использовал 4 байта и 8 байтов выравнивания. Выравнивание в 4 байта дало мне тот же результат, что и приведенный выше код, а выравнивание в 8 байтов только ухудшило ситуацию, но я ожидал этого. Интересно, если я переместлю код из setupBuffer в setupWaveIn и не объявляю GCHandle в контексте класса, но в локальном контексте setupWaveIn структура, возвращаемая функцией обратного вызова, кажется, не повреждена. Однако я не уверен, почему это так и как я могу использовать эти знания для исправления своего кода. Забудьте об этом. Я перепутал вещи со старым кодом, который использовал.

Я бы очень признателен за любые хорошие ссылки на маршаллинг, вызов неуправляемого кода из C # и т. Д. Тогда я был бы очень рад, если бы кто-то мог указать на мои ошибки. Что я делаю неправильно? Почему я не получаю то, что ожидаю.

Ответы [ 3 ]

2 голосов
/ 15 апреля 2010

Ваши декларации P / Invoke безупречны. Однако есть что-то очень странное в дампе WAVEHDR, который вы опубликовали. Отсутствует поле lpData. Если вы вставите это, все числа будут правильно выстроены (т.е. lpData = 0x19904c00, dwBufferLength = 0x0000fa00 и т. Д.).

Не уверен, как это произошло, но он почему-то использует неправильное объявление WAVEHDR. WinMM.WinMM должен быть подсказкой.

1 голос
/ 15 апреля 2010

PInvoke.Net - это место, где можно найти объявления PInvoke. Эта страница описывает метод waveInAddBuffer и его эквивалент C #, а также ссылки на WAVEHDR.

Я посмотрел на различные методы, которые вы используете, но не смог найти ничего полезного в вашем случае. Разница с версией PInvoke.net и вашей в том, что PInvoke использует свойство CharSet StructLayout, но я думаю, что это не имеет отношения.

Хорошая книга по теме взаимодействия: NET-and-COM

0 голосов
/ 16 апреля 2010

Хорошо, я понял это. Весь мой код был в основном правильным. Однако я облажался со структурой 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();
            }
    }

Спасибо за вашу помощь. Я надеюсь, что кто-то может использовать код, который мне удалось придумать.

...