Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Marshal.SizeOf Giving The Wrong Size

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;
};
like image 1000
Snipe3000 Avatar asked Feb 22 '18 21:02

Snipe3000


1 Answers

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.

like image 165
Mark Benningfield Avatar answered Oct 05 '22 15:10

Mark Benningfield