Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PInvoke - Marshal an array of structs from pointer

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.

like image 1000
ScottN Avatar asked Jul 28 '16 23:07

ScottN


1 Answers

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.

like image 114
David Heffernan Avatar answered Nov 08 '22 09:11

David Heffernan