PInvoke - Marshal an array of structs from pointer
Asked Answered
D

1

6

I'm attempting to follow the answer at this question

My struct looks like this in C

typedef struct drive_info_t {
    unsigned char drive_alias[32];
} drive_info_t;

My function looks like this in C

unsigned int get_drive_info_list(drive_info_t **list, unsigned int *item_count) {
    //fill list in native C

    //print out in native C
    printf("list.alias - %s\r\n",list[i]->drive_alias);
}

My C# struct looks like this

[StructLayout(LayoutKind.Sequential)]
public struct drive_info_t
{
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
    public byte[] drive_alias;
}

My C# function declaration looks like this

[DllImport("mydll.dll", EntryPoint = "get_drive_info_list", CallingConvention = CallingConvention.Cdecl)]
public static extern uint GetDriveInfoList(out System.IntPtr ptr_list_info, out System.IntPtr ptr_count);

I'm calling C# function like this

IntPtr ptr_list_info = IntPtr.Zero;
IntPtr ptr_cnt = IntPtr.Zero;

ret = api.GetDriveInfoList(out ptr_list_info, out ptr_cnt);

I'm marshaling the returned pointers like this

nAlloc = ptr_cnt.ToInt32();

int szStruct = Marshal.SizeOf(typeof(api.drive_info_t));
api.drive_info_t[] localStructs = new api.drive_info_t[nAlloc];

for (int i = 0; i < nAlloc; i++)
{
    localStructs[i] = (api.drive_info_t)Marshal.PtrToStructure(ptr_list_info, typeof(api.drive_info_t));
    ptr_list_info = new IntPtr(ptr_list_info.ToInt32() + (szStruct));
}

Printing the struct alias like this

for (uint i = 0; i < localStructs.Length; i++)
{    
    Console.WriteLine("list.alias - {0}", System.Text.Encoding.Default.GetString(localStructs[i].drive_alias));
}

Thanks for staying with me..

This is what my output looks like on a console application in C#. You can see the native C dll printing to the console it's values, but my marshaling is messing up somewhere:

======================== C values ============================
list.alias - drv1
list.alias - drv2
list.alias - drv3
list.alias - drv4
======================== C# values ============================
list.alias - drv1
list.alias - o£Q95drv2
list.alias -         o£Q95drv3
list.alias -                 o£Q95drv4

I have no clue where this garbage text and offset is coming from.

I'm responsible for the .Net side, other team members can change the native C as needed if required, but native C changes need to be cross-platform OSX/Windows/Linux.

Thanks in advance.

Disabled answered 28/7, 2016 at 23:11 Comment(8)
Out of curiosity, what happens if you change [StructLayout(LayoutKind.Sequential)] to [StructLayout(LayoutKind.Sequential, Pack=5)]?Neurotomy
Pack = 5 is not a valid value, also I've tried going through 1, 2, 4, 8 values with no change.Disabled
I was wondering... If you change the IntPtr to UIntPtr and all references of ToInt32 to UInt32. I know sometimes it can seems unimportant but...Bullins
When I switch to UIntPtr, PtrToStructure yells at me. I tried to use .ToPointer but it still errored.Disabled
@DavidHeffernan the struct on the managed size is 32, the struct on unmanaged size is 32. How are you determining 36?Disabled
Yes, this is what I did printf("list size - %d\r\n", sizeof(drive_info_t)); and it printed list size - 32Disabled
That was mis-direction. Sorry. See my answer.Tema
At least the 2nd argument is wrong, it is ref int and not out IntPtr. And you marshal it wrong, you assume it is drive_info_t[] but it looks like drive_info_t*[]. Get ahead by declaring drive_info_t as a class instead of a struct and declaring the 1st argument as drive_info_t[], no ref or out. And you need to get on the phone with the C people and hammer out who is responsible for allocating and destroying the array, that better be you, and how you can tell that your array is too short.Misogynist
T
2

This is a perfect example of why the types of the arguments do not define the interface. Consider

drive_info_t **list

This could be a pointer to an array of structs. In which case presumably the array is allocated by the callee. Which is why a pointer to array is used as opposed to an array.

Or it could be an array of pointers to struct. Here the array would be allocated by caller and the structs could be allocated by either callee or caller. No way for us to tell.

We can discern that your interface accepts an array of pointers to struct. Look at this code:

list[i]->drive_alias

Quite clearly list is an array of pointers to struct.

This tells you that your code is wrong. You have followed the wrong template because you mis-identified the semantics. Your next step is to return to the documentation for the interface and any example calling code to learn precisely what the semantics are. Who allocates and deallocated what.

Almost certainly you will use IntPtr[] to marshal the array but beyond that it is impossible for us to say what happens. I would guess that the caller allocates the structs but don't rely on guesswork. Read the documentation and example calling code.

Beyond that the second argument should be ref uint or perhaps out uint depending on the semantics of the function. Again, that's something that we cannot work out.

Tema answered 30/7, 2016 at 6:10 Comment(1)
Almost certainly you will use IntPtr[] to marshal the array, yes, I ended up changing the declaration to [In,Out] System.IntPtr[] ptr_list_info and also the 2nd parameter to uint. Thanks!Disabled

© 2022 - 2024 — McMap. All rights reserved.