views:

154

answers:

2

As the title suggests, I'm trying to write some C# code to interop with a C DLL (It's a Windows device driver, basically). The error code function signature in C is:

UINT DLLErrorMsg( DWORD errorCode, char * pBuf, UINT nSize );

nSize is the size of pBuf. If nSize can fit the error message, it is copied into pBuf and 0 returned, otherwise (the buffer was too small and) the return value is the minimum size the buffer needs to be to fit the error message.

I've tried some variants of the following:

internal class DLLWrapper
{
    [DllImport(_libName, EntryPoint="DLLErrorMsg", CallingConvention=CallingConvention.Cdecl)]
    public static extern UInt32 GetErrorMessage(UInt32 dwError, ref char * pBuf, UInt32 nBufSize);
}

The client code at the moment looks something like this:

GetError( UInt32 errorCode )
{
    char[] errorMsg = new char[bufSize];            
    UInt32 moreChars = DLLWrapper.GetErrorMessage(errorCode, out errorMsg, bufSize);
    if (moreChars > 0)
    {
        errorMsg = new char[moreChars];
        TWLDLLInterface.GetErrorMessage(errorCode, out errorMsg, moreChars);
    }
}

But I get an exception at the call to GetErrorMessage:

An unhandled exception of type 'System.ArgumentException' occurred in NdevInterface.dll

Additional information: Method's type signature is not Interop compatible.

I've also tried playing around with IntPtr and trying Marshal.IntPtrToStringAuto(), but that wasn't helpful because I need to allocate the buffer, and I obviously can't cast a char[] into an IntPtr.

I've tried searching through the MSDN and general internet for tips on how to do this, but most of it seems to be a little different than what I'm trying.

What am I doing wrong?

+1  A: 

You can marshal this using a StringBuilder. For details, see this P/Invoke intro:

[DllImport(_libName, EntryPoint="DLLErrorMsg", CallingConvention=CallingConvention.Cdecl)]
public static extern UInt32 GetErrorMessage(UInt32 dwError, StringBuilder pBuf, UInt32 nBufSize);

Just make sure the StringBuilder has been constructed with enough memory for the pBuf to get filled in.

Reed Copsey
A: 

This example is very similar to what you're trying to achieve.

I think you want this:

internal class DLLWrapper
{
    [DllImport(_libName, EntryPoint="DLLErrorMsg", CallingConvention=CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
    private static extern UInt32 GetErrorMessage(UInt32 dwError, StringBuilder * pBuf, UInt32 nBufSize);

    public static UInt32 GetErrorMessage( UInt32 dwError, out string msg)
    {
        uint buffersize = 1024;
        StringBuilder sb = new StringBuilder((int)buffersize);
        uint result = GetErrorMessage( dwError, sb, buffersize );
        msg = sb.ToString();
        return result;
    }
}
Koert
That's actually incorrect. You don't want the "*" after StringBuilder - you pass the reference directly, and not a pointer.
Reed Copsey