0

I am attempting to marshal a byte array as a struct in C#. I believe that I am running into an issue regarding how my struct is being packed/padded that is resulting in some unexpected results. For example, I have the following byte array:

byte[] bytes = { 0x07, 0x01, 0x01, 0x01, 0x00, 0xb6, 0xa6, 0xb8, 0x00, 0x90, 0x00, 0x00 };

And am trying to to marshal as my struct:

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct mystruct
{
    [MarshalAs(UnmanagedType.I1)]
    public byte a;
    [MarshalAs(UnmanagedType.I1)]
    public byte b;
    [MarshalAs(UnmanagedType.I1)]
    public byte c;
    [MarshalAs(UnmanagedType.I1)]
    public byte d;
    [MarshalAs(UnmanagedType.U4)]
    public uint e;
    [MarshalAs(UnmanagedType.U2)]
    public ushort f;
    [MarshalAs(UnmanagedType.I1)]
    public byte g;
    [MarshalAs(UnmanagedType.I1)]
    public byte h;
}

With this code:

GCHandle gcHandle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
mystruct output = (mystruct)Marshal.PtrToStructure(gcHandle.AddrOfPinnedObject(), typeof(mystruct));

However, the byte offsets are always wrong following the 4 byte uint, no matter what I have tried in regard to specifying the StructLayout. E.g. where I would expect mystruct.f = 0x0090 = 144, I am getting mystruct.f = 0x9000 = 36864

What am I doing wrong here? Should I be taking a different approach? Is it not possible to pack the struct in such a way that no additional padding is added?

Tony
  • 69
  • 8
  • 1
    The bytes `0x00 0x90` represent the number `0x9000` in [little-endian](https://en.wikipedia.org/wiki/Endianness) that your computer uses. Similarly, your `.e` is `0xb8a6b600` as opposed to `0x00b6a6b8` you probably expected. – GSerg Apr 21 '23 at 18:45
  • Thanks @GSerg, I didn't consider endianness. What would be the recommended way to convert endianness of a byte array for this type of struct in C#? – Tony Apr 21 '23 at 18:51
  • 2
    A byte array does not have endiannes. It's when you instruct the computer to interpret a series of bytes as a longer unit that endiannes comes into play. [Reverse the endiannes](https://stackoverflow.com/a/51460573/11683) for all non-byte members then after you have read your structure. – GSerg Apr 21 '23 at 18:53
  • 3
    You also don't need all those `MarshalAs` attributes, every single one is implied. In fact you don't even need marshalling, you're paying an extreme cost for no reason, just map your structure over the bytes directly (`MemoryMarshal.AsRef<>`). – Blindy Apr 21 '23 at 18:56
  • @GSerg Excellent, that seems to have solved my problem. In a case like this, is the best approach to then manually reverse the endiannes of each individual member of that struct, or is there a slicker way to do this? – Tony Apr 21 '23 at 19:19
  • There is - not doing it in the first place. How do you generate that byte array and why is it in big-endian? – GSerg Apr 21 '23 at 19:41
  • The byte array is coming over a network in a UDP packet as a protocol that I do not have control over. – Tony Apr 21 '23 at 20:00

1 Answers1

1

Check the value of BitConverter.IsLittleEndian, for me it returns true and I get the same value for mystruct.f - 0x9000 (36864). From wiki:

A little-endian system, in contrast, stores the least-significant byte at the smallest address

So in 0x00, 0x90 0x00 will have the smallest address of the two, hence the elements are representing 0x9000 number.

Guru Stron
  • 102,774
  • 10
  • 95
  • 132