Reputation: 75
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++)
//{
//}
}
}
Upvotes: 1
Views: 1361
Reputation: 613053
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.
AllocHGlobal
or AllocCoTaskMem
.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.
Upvotes: 1