Marshal.SizeOf structure returns excessive number

2020-04-06 13:01发布

问题:

I have the following structure

[StructLayout(LayoutKind.Sequential)]
public struct SFHeader
{

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 5)]
    public string FileName;

    public int Offset;

    public short Size;

    public byte Flags;

    public byte Source;


    public long LastWriteTime;


    public byte[] GetBytes()
    {
        int size = Marshal.SizeOf(this);
        var buffer = new byte[size];
        IntPtr ptr = Marshal.AllocHGlobal(size);

        Marshal.StructureToPtr(this, ptr, true);
        Marshal.Copy(ptr, buffer, 0, size);
        Marshal.FreeHGlobal(ptr);

        return buffer;
    }


    public static SFHeader FromBytes(byte[] buffer)
    {
        var str = new SFHeader();
        int size = Marshal.SizeOf(str);

        IntPtr ptr = Marshal.AllocHGlobal(size);
        Marshal.Copy(buffer, 0, ptr, size);
        str = (SFHeader)Marshal.PtrToStructure(ptr, str.GetType());
        Marshal.FreeHGlobal(ptr);

        return str;
    }

}

I need to convert my structure to an array of byte (to send as packet with socket), so I use the GetBytes method, but it returns an array of 24 bytes instead of an array of 21 bytes:

  • Filename (string): 5 bytes
  • Offset (int): 4 bytes
  • Size (short): 2 bytes
  • Flags (byte): 1 byte
  • Source (byte): 1 byte
  • LastWriteTime (long): 8 bytes

So: 5+4+2+1+1+8 = 21 bytes.
This happens because Marshal.SizeOf returns 24, why? And it seems that the the bytes in excess are placed after the bytes of the string, in fact for example the following structure:

var header = new SFHeader()
{
   FileName = "aaaa",
   Offset = 1,
   Size = 1
};

is converted to the following buffer:

[0] = 97
[1] = 97
[2] = 97
[3] = 97
[4] = 0
[5] = 0
[6] = 0
[7] = 0
[8] = 1
[9] = 0
[10] = 0
[11] = 0
[12] = 1
[13] = 0
... The following are all zero (0)

The fifth, sixth and seventh are the bytes in excess. How can I solve this problem?

回答1:

You're running into a byte-alignment issue. In an attempt to keep fields on word boundaries for speed of access, the compiler is padding your string with 3 extra bytes. To fix this, use the Pack field of the StructLayoutAttribute.

[StructLayout(LayoutKind.Sequential, Pack=1)]  // notice the packing here
public struct SFHeader
{
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 5)]
    public string FileName;

    public int Offset;

    public short Size;

    public byte Flags;

    public byte Source;

    public long LastWriteTime;
}


回答2:

You could use a fixed size buffer instead of a string.

[StructLayout(LayoutKind.Sequential)]
public unsafe struct SFHeader
{
    public fixed char FileName[5];
    public int Offset;
    public short Size;
    public byte Flags;
    public byte Source;
    public long LastWriteTime;

    public byte[] GetBytes()
    {
        //omitted
    }

    public static SFHeader FromBytes(byte[] buffer)
    {
        //omitted
    }
}


标签: c# struct sizeof