简体   繁体   中英

PInvoke / Marshalling with a variable length array of structures

I've been struggling with marshalling a structure in C# for the last couple of days. Hoping someone with a bit more experience can assist (structure definitions were shortened a bit so it's not as much reading).

C HBAAPI definition

HBA_STATUS HBA_GetFcpTargetMapping(
    HBA_HANDLE      handle,
    HBA_FCPTARGETMAPPING *pmapping
);

typedef struct HBA_FCPTargetMapping {
    HBA_UINT32      NumberOfEntries;
    HBA_FCPSCSIENTRY    entry[1];       /* Variable length array
                                         * containing mappings */
} HBA_FCPTARGETMAPPING, *PHBA_FCPTARGETMAPPING;

typedef struct HBA_FcpScsiEntry {
    HBA_SCSIID      ScsiId;
} HBA_FCPSCSIENTRY, *PHBA_FCPSCSIENTRY;

typedef struct HBA_ScsiId {
    char        OSDeviceName[256];
    HBA_UINT32      ScsiBusNumber;
} HBA_SCSIID, *PHBA_SCSIID;

My definitions in C# are:

[DllImport("hbaapi.dll")]
static extern Uint32 HBA_GetFcpTargetMapping(
    IntPtr      handle,
    IntPtr      fcpmapping
);

[StructLayout(LayoutKind.Sequential)]
public struct HBA_FCPTARGETMAPPING
{
   public Uint32_ NumberOfEntries,
   public HBA_FCPSCSIENTRY SCSIEntry
}

[StructLayout(LayoutKind.Sequential)]
public struct HBA_FCPSCSIENTRY
{
   public HBA_SCSIID ScsiId
}

[StructLayout(LayoutKind.Sequential)]
public struct HBA_SCSIID
{
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)]
    byte[]      OSDeviceName;
    Uint32      ScsiBusNumber;
}

I can get the first SCSIEntry, but not subsequent ones. I understand that the definition is a variable length array, but I can't figure out how to declare it properly, or Marshal the data back into the Managed Structure.

The following works, but obviously only gets 1 SCSIEntry

//Allocate only one, supposed to recall with the appropriate allocated size, using NumberOfEntries
IntPtr buffer = Marshal.AllocHglobal(Marshal.SizeOf(typeof(HBA_FCPTARGETMAPPING)));
Uint32 status = HBA_GetFcpTargetMapping(hbaHandle, buffer);
HBA_FCPTARGETMAPPING fcpTgtMapping = (HBA_FCPTARGETMAPPING)Marshal.PtrtoStructure(buffer, typeof(HBA_FCPTARGETMAPPING));

Edit -- does this look right? How do I get the array of SCSIEntry?

[StructLayout(LayoutKind.Sequential)]
public struct HBA_FCPTARGETMAPPING
{
    public UInt32 NumberOfEntries;
    public IntPtr SCSIEntry;    /* Variable length array containing mappings*/
}

//Alloc memory for 1 FCPTargetMapping to get the number of entries
int singleBufferSize = Marshal.SizeOf(typeof(HBA_FCPTARGETMAPPING));
IntPtr singleBuffer = Marshal.AllocHGlobal(singleBufferSize);
uint singleResult = HBA_GetFcpTargetMapping(hbaHandle, singleBuffer);
HBA_FCPTARGETMAPPING singleFCPTargetMapping = (HBA_FCPTARGETMAPPING)Marshal.PtrToStructure(singleBuffer, typeof(HBA_FCPTARGETMAPPING));
int numberOfEntries = int.Parse(singleFCPTargetMapping.NumberOfEntries.ToString());

//more memory required
if (singleResult == 7)
{

    //Now get the full FCPMapping
    int fullBufferSize = Marshal.SizeOf(typeof(HBA_FCPTARGETMAPPING)) + (Marshal.SizeOf(typeof(HBA_FCPSCSIENTRY)) * numberOfEntries);
    IntPtr fullBuffer = Marshal.AllocHGlobal(fullBufferSize);
    uint fullResult = HBA_GetFcpTargetMapping(hbaHandle, fullBuffer);
    if (fullResult == 0)
    {
        HBA_FCPTARGETMAPPING fullFCPTargetMapping = (HBA_FCPTARGETMAPPING)Marshal.PtrToStructure(fullBuffer, typeof(HBA_FCPTARGETMAPPING));
        //for (uint entryIndex = 0; entryIndex < numberOfEntries; entryIndex++)
        //{

         //}
    }
}

You cannot get the marshaller to marshal a variable length struct. It simply is not capable of that. Which means that you will need to marshal it manually.

  1. Allocate the struct using AllocHGlobal or AllocCoTaskMem .
  2. When calculating the size of the struct make sure you account for any padding.
  3. Marshal the array manually using PtrToStructure and StructureToPtr and pointer arithmetic.

It's well worth having a struct with a single element of the array handy. Just as you have in the code in the question. You can use that to marshal the main part of the struct and let the marshaller handle layout and padding. Then use OffsetOf to find the offset of the variable length array and marshal that element by element.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM