Home > OS >  Marshaling struct with IntPtr to buffer from C# to C dll
Marshaling struct with IntPtr to buffer from C# to C dll

Time:07-09

I've been banging my head all day and hope someone can help. I need to marshal a managed data structure to an unmanaged C dll. When I look at all the memory it appears that what I'm doing is working, but the C dll (a black box to me) is returning an error indicating the data is corrupt. Can anyone point out my error?

C declarations

   typedef struct _TAG_Data
   {
       void *data;      // Binary data
       uint32_t size;   // Data size bytes
   } Data;
    
        
    // Parse binary data, extract int value
    ParseData(Data ∗ result, uint64_t parameter, int32_t ∗ value)

Managed object:

     public class MyData
    {
        public byte[] data;
        public UInt32 size;
    }

Packed equivalent for moving to unmanaged memory:

[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct MyData_Packed
{
    public IntPtr data;
    public UInt32 size;
}

At this point I have a managed "MyData" struct called MyResult with valid data that needs to go into the dll. Here's what I'm doing:

       [DllImport("Some.dll", EntryPoint = "ParseData", SetLastError = true, CharSet = CharSet.Ansi)]
       private static extern Int32 ParseData_Native(IntPtr result, UInt64 parameter, ref Int32 value);


        IntPtr MyResultPackedPtr = new IntPtr();
        MyData_Packed MyResultPacked = new MyData_Packed();

        // Copy "MyResult" into un-managed memory so it can be passed to the C library.  

        // Allocate un-managed memory for the data buffer
        MyResultPacked.data = Marshal.AllocHGlobal((int)MyResult.size);

        // Copy data from managed "MyResult.data" into the unmanaged "MyResultPacked.data"
        Marshal.Copy(MyResult.data, 0, MyResultPacked.data, (int)MyResult.size);
        MyResultPacked.size = MyResult.size;

        // Allocate unmanaged memory for the structure itself
        MyResultPackedPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(MyData_Packed)));
        
        // Copy the packed struct into the unmanaged memory and get our pointer
        Marshal.StructureToPtr(MyResultPacked, MyResultPackedPtr, false);

        // Pass our pointer to the unmanaged struct, which points to the unmanaged data buffer, to the C dll
        Int32 tmp = 0;
        ErrorCode = ParseData_Native(MyResultPackedPtr, parameter, ref tmp);

When I look at the unmanaged memory pointed to by MyResultPacked.data, the data is correct so the copy was good. And when I look at MyResultPackedPtr in memory, the first 8 bytes are the address of the same unmanaged memory pointed to by MyResultPacked.data (64-bit machine), and the next 4 bytes are the proper size of the data. So it appears that MyResultPackedPtr points to a valid copy of MyResultPacked. But the return value from ParseData() indicates my data must be corrupt, so I must be doing something wrong.

To take it a step further, I wrote the same code 100% in C and it works. And the data in the binary buffer in C matched the data in the binary buffer in C#, going by the memory watch feature in Visual Studio, so it appears my data handling is correct. Which makes me think something is wrong with the way I'm passing MyResultPackedPtr to the dll. Unfortunately I don't have the source for the dll and cannot step into it. Can anyone offer a suggestion on what to try next?

CodePudding user response:

I don't see why you need any of this custom marshalling code in the first place. You should be able to pass the struct with the byte[] array directly, and the marshaller will sort out the copying.

You also need to set the calling convention correctly.

[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct MyData_Packed
{
    public byte[] data;
    public UInt32 size;
}

[DllImport("Some.dll", EntryPoint = "ParseData", SetLastError = true, CallingConvention = CallingConvention.CDecl)]
private static extern int ParseData_Native(ref MyData_Packed result, ulong parameter, out int value);
var MyResultPacked = new MyData_Packed
{
    data = MyResult,
    size = MyResult.size,
};

ErrorCode = ParseData_Native(ref MyResultPacked, parameter, out var tmp);
  • Related