How do I marshall a pointer to a pointer of an array of structures?

后端 未结 2 1272
盖世英雄少女心
盖世英雄少女心 2020-12-10 14:59

My C declarations are as follows:

int myData(uint myHandle, tchar *dataName, long *Time, uint *maxData, DATASTRUCT **data);

typedef struct {
  byte Rel;
  _         


        
相关标签:
2条回答
  • 2020-12-10 15:07

    Okay, it seems as though your native library does the allocation, so really all you need to do is provide a pointer through which you can access the allocated data.

    Change your API definition to (note, I changed the maxData param to uint, long is 64 bits in .NET and 32 bits in native.

    [DllImportAttribute("myData.dll", EntryPoint = "myData")]
    public static extern int myData(uint myHandle, [MarshalAs(UnmanagedType.LPTStr)] string dataName, out uint Time, out uint maxData, out IntPtr pData);
    

    Off the top of my head I can't quite remember if you need the out keyword for the final parameter, but I think so.

    Then, call myData:

    uint nAllocs = 0, time = 0;
    IntPtr pAllocs = IntPtr.Zero;
    myData(1, "description", out time, out nAllocs, out pAllocs);
    

    Now, pAllocs should point to unmanaged memory, to marshal these into managed memory isn't too difficult:

    [StructLayoutAttribute(LayoutKind.Sequential, Pack = 1)]
    public struct DATASTRUCT
    {
        public byte Rel;
        public long Time;
        public byte Validated;
        public IntPtr Data; //pointer to unmanaged string.
    }
    
    
    int szStruct = Marshal.SizeOf(typeof(DATASTRUCT));
    DATASTRUCT[] localStructs = new DATASTRUCT[nAllocs];
    for(uint i = 0; i < nallocs; i++)
        localStructs[i] = (DATASTRUCT)Marshal.PtrToStructure(new IntPtr(pAllocs.ToInt32() + (szStruct * i)), typeof(DATASTRUCT));
    

    And now you should have an array of local structs.

    A point to note You may need to set your project to compile as x86, to standardize the size of an IntPtr to 4 bytes (DWORD) instead of AnyCPU's default 8.

    0 讨论(0)
  • 2020-12-10 15:25

    A pointer to a pointer could be represented in your dllimport declaration as ref IntPtr data, so your declaration would become:

    [DllImportAttribute("myData.dll", EntryPoint = "myData")]
    public static extern int myData(uint myHandle, [MarshalAs(UnmanagedType.LPTStr)] string dataName, out long Time, out uint maxData, ref IntPtr data);
    

    (As an aside, I think a long in C is just equivalent to an int in C#. Long in C# is an Int64, which would be a long long in C)

    Marshalling your DATASTRUCT[] to an IntPtr can be done using the GCHandle class

    DATASTRUCT [] dataInformation = new DATASTRUCT[3];
    GCHandle gch = GCHandle.Alloc(dataInformation , GCHandleType.Pinned);
    IntPtr ptr = gch.AddrOfPinnedObject();
    myData(myHandle, dataToShow, out Time, out maxData, ref ptr);
    //It's absolutely essential you do this next bit so the object can be garbage collected again, 
    //but it should only be done once the unmanaged code is definitely done with the reference.    
    gch.Free(); 
    

    Using the Marshal class and the StructureToPtr or Copy methods of it would also be an option but for the purposes of proving the concept at least the GCHandle should do the trick, it's just not ideal for scenarios where the unmanaged code does long running operations because you've pinned this object in place and the GC can't move it until you free it.

    0 讨论(0)
提交回复
热议问题