The code below is giving the wrong size when using Marshal.SizeOf, but I'm not sure why.
Here is the Struct I'm trying to get the size of:
//[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
//[Serializable]
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct BLEGenericMsg
{
public BLEMessageHdr msg_hdr;
public byte[] msg_body;
public BLEGenericMsg(int messageSize)
{
msg_hdr = new BLEMessageHdr();
msg_body = new byte[messageSize];
}
};
Here is the code that populates the struct and calls the serialize function:
BLEGenericMsg hostKeyMsg = new BLEGenericMsg(serializedPublicBytes.Length);
hostKeyMsg.msg_hdr.msg_id = MESSAGE_BASE_EVENT + EVENT_HOST_PUBLIC_KEY;
hostKeyMsg.msg_body = serializedPublicBytes;
//Only get the size of the body for the entire message, not counter or header
hostKeyMsg.msg_hdr.msg_body_sz = (uint)hostKeyMsg.msg_body.Length;
BluetoothLEHardwareInterface.Log("public Key Size: " + hostKeyMsg.msg_hdr.msg_body_sz + "\n");
byte[] temp = Serialize(hostKeyMsg);
BluetoothLEHardwareInterface.Log("temp Size: " + (uint)temp.Length + "\n");
Here is the serialize function that is getting the size of the struct:
public static byte[] Serialize<T>(T s)
where T : struct
{
var size = Marshal.SizeOf(typeof(T));
BluetoothLEHardwareInterface.Log("BLEGenericMsg Size: " + size + "\n");
var array = new byte[size];
var ptr = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(s, ptr, true);
Marshal.Copy(ptr, array, 0, size);
Marshal.FreeHGlobal(ptr);
return array;
}
The size of serializedPublicBytes is 91 bytes, the rest of the struct is 6 bytes. So I'm expecting the Marshal.SizeOf to be 97 bytes, but instead it is showing only about 14 or 16 bytes. I tried giving the size of msg_body at instantiation, but that didn't make a difference. What am I missing?
**edit Here is here is the BLEMessageHdr struct:
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct BLEMessageHdr
{
public ushort msg_id;
public uint msg_body_sz;
};
The Marshal.SizeOf()
method is not returning the wrong size. In the structure you define:
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct BLEGenericMsg
{
public BLEMessageHdr msg_hdr;
public byte[] msg_body;
public BLEGenericMsg(int messageSize)
{
msg_hdr = new BLEMessageHdr();
msg_body = new byte[messageSize];
}
};
the msg_body
member is known as a "Flexible Array Member" (FAM) in C. It is an illegal construct in C++. Because it is illegal in C++, and because of the inherent uncertainties in the C standard (§ 6.7.2.1) with regard to the instantiation of a struct that contains a FAM, the Marshal class simply does not accept them for interop with unmanaged code.
The way array members are usually marshalled is with the MarshalAsAttribute
, like so:
[MarshalAs(UnmanagedType.ByValArray, SizeConst=N)]
public byte[] msg_body;
where "N" represents the explicitly declared size of the array. Without this attribute, the msg_body
member is treated as a pointer by the Marshal class. So, the size that Marshal.SizeOf()
is returning is correct. Your generic Serialize()
method won't work for structs that have a FAM.
You could modify it to copy over the contents of the FAM manually after the rest has been copied by the Marshal class, but this seems like a rather awkward approach to binary serialization for a managed struct.
// specify the name of the FAM and use reflection to get the value
// THIS ASSUMES that the FAM is always a byte[]
public static byte[] Serialize<T>(T s, string fam) where T : struct
{
Type tt = typeof(T);
// Reflection will get you the bytes in the FAM
FieldInfo fi = tt.GetField(fam);
byte[] famBytes = (byte[])fi.GetValue(s);
// Get the field offset that corresponds to the unmanaged layout for
// the FAM, according to the marshaller
int offset = (int)Marshal.OffsetOf(tt, fam);
var size = Marshal.SizeOf(tt) + famBytes.Length;
BluetoothLEHardwareInterface.Log("BLEGenericMsg Size: " + size + "\n");
var array = new byte[size];
var ptr = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(s, ptr, true);
Marshal.Copy(ptr, array, 0, size);
Marshal.FreeHGlobal(ptr);
// Now you're done with the marshalling, just copy over the contents of the
// byte[] to your resulting array, starting at the correct offset
Array.Copy(famBytes, 0, array, offset, famBytes.Length);
return array;
}
Naturally, you will have to likewise modify the Deserialize()
method to deal with structs that have a FAM.
AGAIN, this seems like an awkward approach to this problem. You may want to really reconsider this approach.