Search code examples
c#cpinvoke

how to pass a pointer to C# struct to a method in C


I want to use a c++ dll in c#. I'm using [DllImport] to call the method. I'm having trouble passing struct to a method.

I have a C struct:

typedef struct
{
DWORD TopPoint;
DWORD EndPoint;
WORD dwCount;
MYFUNC_NUMERIC11 *pGetData;
} MYFUNC_BUFFERNORMAL;

MYFUNC_NYMERIC11 is another struct.

typedef struct
{
BYTE Sign; // Sign ("±")
BYTE Integer[3]; // 3-digit integer (no zero suppression)
BYTE Period; // Decimal point (".")
BYTE Decimal[6]; // 6-digit decimal number
} MYFUNC_NUMERIC11;

I have written a C# struct to mimic this.

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public unsafe struct MYFUNC_BUFFERNORMAL
{
    public uint TopPoint;
    public uint EndPoint;
    public ushort Count;
    public IntPtr pGetData;
}

A pointer to the struct is an argument in a method. C# function is:

[DllImport("MYFUNC_DLL.dll", EntryPoint = "MYFUNC_GetData", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall, ThrowOnUnmappableChar = true)]
public static extern int MYFUNC_GetData(IntPtr myfuncHandle, UInt32 dwIO, ref IntPtr pBufferNormal, Byte bccFlg);

This is the method in C:

MYFUNC_STATUS MYFUNC_GetData(MYFUNC_HANDLE myfuncHandle, DWORD dwOut, MYFUNC_BUFFERNORMAL *pBufferNormal , BYTE bccFlg)

The return type is cast to an enum, which has an interpretation. The struct parameter is invalid. I've tried to allocate memory using Marshal.AllocHGlobal(...), but the parameter is still invalid, i.e. there is no error during compilation but the value returned is incorrect.

I've spent quite a few hours on this, still unable to figure out what to do. A lot of similar questions exist already, like here: How do I convert c struct from dll to C# or here: How to pass C# array to C++ and return it back to C# with additional items?, but I, somehow, still haven't figured out a way.


Solution

  • Something like this should work, at least with one element in the array (is it an array?). For an array, you will have to allocate sizeof * count of elements and marshal (StructureToPtr) each element at its offset.

    var num = new MYFUNC_NUMERIC11();
    num.Integer = new byte[] { 1, 2, 3 };
    num.Decimal = new byte[] { 4, 5, 6, 7, 8, 9 };
    num.Sign = 10;
    num.Period = 11;
    
    var buffer = new MYFUNC_BUFFERNORMAL();
    buffer.Count = 1234;
    buffer.EndPoint = 5678;
    buffer.TopPoint = 9;
    buffer.pGetData = Marshal.AllocCoTaskMem(Marshal.SizeOf(num));
    try
    {
        Marshal.StructureToPtr(num, buffer.pGetData, false);
        MYFUNC_GetData(Whatever, 0, ref buffer, 0);
    }
    finally
    {
        Marshal.FreeCoTaskMem(buffer.pGetData);
    }
    

    With these definitions.

    [StructLayout(LayoutKind.Sequential)]
    public struct MYFUNC_BUFFERNORMAL
    {
        public uint TopPoint;
        public uint EndPoint;
        public ushort Count;
        public IntPtr pGetData;
    }
    
    [StructLayout(LayoutKind.Sequential)]
    public struct MYFUNC_NUMERIC11
    {
        public byte Sign;
        
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
        public byte[] Integer;
        
        public byte Period;
        
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
        public byte[] Decimal;
    }
    
    // check calling convention
    [DllImport(@"MYFUNC_DLL.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern MYFUNC_STATUS  MYFUNC_GetData(IntPtr myfuncHandle, uint dwIO, ref MYFUNC_BUFFERNORMAL pBufferNormal, byte bccFlg);