Home > Software engineering >  Pass array of struct to C from C# via struct pointer
Pass array of struct to C from C# via struct pointer

Time:06-18

I want to pass an array of struct from C# to C dll.

The struct definition in C:

typedef struct{
    BYTE name[32];
    DWORD age;
}DATA;

I marshalled the struct as follow:

[StructLayout(LayoutKind.Sequential)]
public struct DATA
{
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
    public byte[] name;
    public int age;
}

The C function definition that I want to pass on:

void test_data(DATA * pStruct, DWORD length){
    _tprintf("test start\n");
    
    if (!pData)
    {
        _tprintf("pData is NULL\n");
    }
    unsigned int i,j;
    
    for(i = 0; i < length; i  ){
        _tprintf("name: ");
        for(j = 0; j < 32; j  ){
            _tprintf("%x ",pData[i].name[j]);
        }
        _tprintf("\n");
        _tprintf("age: %d",pData[i].age);
        _tprintf("\n");
    }

    _tprintf("test finish\n");
}

The function signature in C#:

[DllImport("a.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void test_data(ref DATA pData, int length);

The setup in C#:

int length = 10;
DATA[] arr = new DATA[length];
for(int i = 0; i < length; i  ){
  arr[i].name = new byte[32];
  arr[i].age = 10;
}

test_data(ref arr[0], length);

But I got these output:

test start
name: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
age: 10
name: 7c 67 9a 2 a 0 0 0 1 0 0 0 2 0 0 0 eb 9 77 67 a4 eb 8f 0 0 0 0 0 2 0 0 0
age: 43673448
test finish

The first one is correct, but the second one seems weird, and I think there is something wrong with how I passed the reference or doing the setup.

I was inspired from the pinvoke web in SCardTransmit function here where it passed the byte array in C# to the byte * in C using ref rapdu[0]

How to correctly do this ?

CodePudding user response:

That's not how you marshal an array of structures from managed code to native code.

In your P/Invoke declaration:

[DllImport("a.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void test_data(ref DATA pData, int length);

ref DATA pData marshals to a pointer to a single DATA object. There is no guarantee that the rest of the array will be sent along with it. It could have worked if your structure was blittable, because then marshalling it would simply have involved pinning the array and then passing its address as is to the C function. But, alas, the structure contains an array, which is a reference type. When you pass it to the C function, the marshaler has to do a copy to get a struct with the right layout. So, when you pass ref arr[0], you're only sending one copy, and then your C code walks right off the end of the buffer and you hit undefined behaviour. And, on a more philosophical level, it's also less clear that pData is supposed to be an array.

So instead, to send the whole batch, just declare your argument as an array, as explained in the documentation, and the marshaler will do the rest:

[DllImport("a.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void test_data(DATA[] pData, int length);
  • Related