Marshalling to a native library in C#
- by Daniel Baulig
I'm having trouble calling functions of a native library from within managed C# code. I am developing for the 3.5 compact framework (Windows Mobile 6.x) just in case this would make any difference.
I am working with the waveIn* functions from coredll.dll (these are in winmm.dll in regular Windows I believe). This is what I came up with:
// 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);
}
I read alot about marshalling the last few days, nevertheless I do not get this code working. When my callback function is called (waveIn) when the buffer is full, the hdr structure passed back in wavehdr is obviously corrupted. Here is an examlpe of how the structure looks like at that point:
- 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
This obiously is not what I expected to get passed. I am clearly concerned about the order of the fields in the view. I do not know if Visual Studio .NET cares about actual memory order when displaying the record in the "local"-view, but they are obviously not displayed in the order I speciefied in the struct.
Then theres no data pointer and the bufferLength field is far to high. Interestingly the bytesRecorded field is exactly 64000 - bufferLength and bytesRecorded I'd expect both to be 64000 though. I do not know what exactly is going wrong, maybe someone can help me out on this. I'm an absolute noob to managed code programming and marshalling so please don't be too harsh to me for all the stupid things I've propably done.
Oh here's the C code definition for WAVEHDR which I found here, I believe I might have done something wrong in the C# struct definition:
/* 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;
If you are used to work with all those low level tools like pointer-arithmetic, casts, etc starting writing managed code is a pain in the ass. It's like trying to learn how to swim with your hands tied on your back.
Some things I tried (to no effect):
.NET compact framework does not seem to support the Pack = 2^x directive in [StructLayout].
I tried [StructLayout(LayoutKind.Explicit)] and used 4 bytes and 8 bytes alignment. 4 bytes alignmentgave me the same result as the above code and 8 bytes alignment only made things worse - but that's what I expected.
Interestingly if I move the code from setupBuffer into the setupWaveIn and do not declare the GCHandle in the context of the class but in a local context of setupWaveIn the struct returned by the callback function does not seem to be corrupted. I am not sure however why this is the case and how I can use this knowledge to fix my code.
I'd really appreciate any good links on marshalling, calling unmanaged code from C#, etc. Then I'd be very happy if someone could point out my mistakes. What am I doing wrong? Why do I not get what I'd expect.