2

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 ?

of32 inc
  • 105
  • 1
  • 7
  • A web search on `c# "MarshalAs" byte array`, we get [amongst others]: https://stackoverflow.com/questions/1354275/marshaling-a-byte-array-to-a-c-sharp-structure At a guess, from that: `public byte[] name;` --> `public byte[] name[32];` ? – Craig Estey Jun 18 '22 at 02:04
  • @CraigEstey That's not how you declare an array in C#. If you wanted to declare a fixed size buffer, you seem to be missing the `fixed` keyword, but that also means declaring your structure as unsafe. – Etienne de Martel Jun 18 '22 at 02:33
  • 1
    You show are you create your array of `DATA`, but not how you create the individual `DATA` objects in the array. I'm particularly interested in how you initialise `name`. – Etienne de Martel Jun 18 '22 at 02:38
  • @EtiennedeMartel Yes (hence "guess"). The link has the correct syntax? So, would it be: `[FieldOffset(0x00)] public fixed byte name[32];` – Craig Estey Jun 18 '22 at 02:42
  • @CraigEstey It's also unnecessary: the linked question is specifically about overlapping fields of reference type, which is forbidden. This should not be needed here, [as the documentation states](https://learn.microsoft.com/en-us/dotnet/framework/interop/default-marshalling-for-arrays#arrays-within-structures). – Etienne de Martel Jun 18 '22 at 02:46
  • @EtiennedeMartel hi thx for commenting, I updated my post. I was able to get pass the error, I was wrong with the array initialization error, but now I got new problem – of32 inc Jun 18 '22 at 03:13

1 Answers1

2

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);
Etienne de Martel
  • 34,692
  • 8
  • 91
  • 111