Daniel Jurado
Daniel Jurado

Reputation: 49

How to Marshal a C struct with a byte * field for C# with DLLImport

I am trying to figure how to correctly Marshal a C structure containing a byte * field so I can send commands to a third-party API from a C# application. However I keep receiving errors complaining about parameters. This is what I have, from C code:

struct MY_STRUCT
{
    int32 Object;            // Object index, dependent of the context
    int32 Cmd;               // Command code
    byte *Params;            // pointer to parameters
};

int32 stdcall SendCommand( int32 DeviceId, MY_STRUCT *Cmd );

This is how I'm currently referencing to it on my C# code:

public struct MY_STRUCT
{
    public Int32 Object;            // Object index, dependent of the context
    public Int32 Cmd;               // Command code
    public IntPtr Params;           // pointer to parameters
};

[DllImport("C:\\Client.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern Int32 SendCommand( Int32 DeviceId, ref MY_STRUCT Cmd );

Int32 CSharpSendCommand( int placa, int chan, int code, String parametros )
    {
        MY_STRUCT cmd = new MY_STRUCT();
        cmd.Cmd = code;
        cmd.Object = chan;
        byte[] bytes = new byte[parametros.Length * sizeof(char)];
        System.Buffer.BlockCopy(parametros.ToCharArray(), 0, bytes, 0, bytes.Length);
        IntPtr p = Marshal.AllocHGlobal(bytes.Length);
        try { 
            Marshal.Copy(bytes, 0, p, parametros.Length);
            cmd.Params = p;
            Int32 ret = Translated.SendCommand( placa, ref cmd );
            if (ret != (int)LibraryStatus.Success)
                Console.Out.Write("Erro: Comando 0x{0} retornou: {1}\n", code, ret);

            return ret;
        }
        finally
        {
            Marshal.FreeHGlobal(p);
        }
    }

Any guesses? It works really fine when sending primitive types like Byte, Int32, so on an so forth...

Upvotes: 0

Views: 382

Answers (1)

Jim Mischel
Jim Mischel

Reputation: 133975

Most likely the problem is that you're confusing bytes and characters. In particular, you have this:

    byte[] bytes = new byte[parametros.Length * sizeof(char)];
    System.Buffer.BlockCopy(parametros.ToCharArray(), 0, bytes, 0, bytes.Length);
    IntPtr p = Marshal.AllocHGlobal(bytes.Length);

This is essentially correct in that you're taking into account that a char is two bytes in length. But then you have:

        Marshal.Copy(bytes, 0, p, parametros.Length);

This is a problem because you're only copying parametros.Length bytes, which is exactly half the length of the bytes array.

I think you want to change that to bytes.Length.

The other problem I see is that you're not supplying anything that says how long that sequence of bytes is (i.e. no cbParams value, or similar), and no null terminator if it's supposed to be treated like a string.

And, as Hans Passant points out, it's unlikely that the C code would understand a bunch of 2-byte "characters", anyway. His suggestion to use Marshal.StringToHGlobalAnsi is probably right on the money.

Upvotes: 3

Related Questions