1

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++)
        //{

         //}
    }
}
1
  • Change from : entry[1]; to : entry[];. You have an array of pointers with the number of pointers determined by the variable NumberOfEntries. The allocated size is sizeOf(Uint32) + sizeOf(NumberOfEntries * sizeOf(HBA_FCPSCSIENTRY). The first entry is the pointer to the array plus an offset of Uint32. Then get the next entry add sizeOf HBA_FCPSCSIENTRY). Commented May 23, 2015 at 2:47

1 Answer 1

1

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.

Sign up to request clarification or add additional context in comments.

1 Comment

The SCSIEntry member should be typed HBA_FCPSCSIENTRY. It's not a pointer. It's an inline array.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.