Маршалинг в собственную библиотеку на C#
-
27-09-2019 - |
Вопрос
У меня возникли проблемы с вызовом функций собственной библиотеки из управляемого кода 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
Хорошо, я понял это.Весь мой код был в основном правильным.Однако я облажался со структурой 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();
}
}
Спасибо за помощь.Я надеюсь, что кто-нибудь сможет использовать код, который мне удалось придумать.
Другие советы
Ваши объявления P/Invoke безупречны.Однако есть что-то очень странно насчет выложенного вами дампа WAVEHDR.Отсутствует поле lpData.Если вы вставите это, все числа выстроятся правильно (т.lpData = 0x19904c00, dwBufferLength = 0x0000fa00 и т. д.).
Не знаю, как это произошло, но почему-то используется неправильное объявление WAVEHDR.WinMM.WinMM должен быть подсказкой.
PInvoke.Net — это место, где можно найти объявления PInvoke.Эта страница описывает метод waveInAddBuffer и его эквивалент на C#, а также ссылки на WAVEHDR.
Я рассмотрел различные методы, которые вы используете, но не нашел ничего полезного в вашем случае.Разница между версией PInvoke.net и вашей заключается в том, что PInvoke использует свойство CharSet StructLayout, но я думаю, это не имеет значения.
Хорошая книга по теме совместимости: NET-и-COM