Search code examples
c#structtcpmarshallingpacket

Marshaling C# struct with array of structs and size param index


I've read several topics about but I still can't understand the real limitation of not being able to convert this structure to byte array easily:

[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct B {
  public int b_a;
}

[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct A {
  public int sizeB;

  [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)]
  public B[] b;
}

I'm writing a TCP communication program so I want to build my S2C packets in a struct and then send them as byte[] so I'm looking for the cheapest and fastest way to achieve this.

I have already tried Marsheling in many ways but there is always some exception in Marshal.SizeOf().

In this example I get the following error: "[...] cannot be marshaled as an unmanaged structure; no meaningful size or offset can be computed."

Struct initialization eg.:

A a = new A();
B[] b = new B[5];

a.sizeB = 5;
a.b = b;

Marshal.SizeOf(a);

Solution

  • You don't have the same control for low level memory access like you would C or C++. You will need to do some manual work when you have an array of undefined length in C#.

    Here are a couple ways of accomplishing that.

    struct B
    {
        public int b_a;
    }
    
    struct A
    {
        public int sizeB;
    
        public B[] b;
    }
    

    The first being a BinaryWriter. This can be faster if your structure does not have a lot of fields.

    static byte[] ConvertToByte(A a)
    {
        using (var ms = new MemoryStream())
        using (var writer = new BinaryWriter(ms))
        {
            writer.Write(a.sizeB);
    
            foreach (var b in a.b)
                writer.Write(b.b_a);
    
            return ms.ToArray();
        }
    }
    

    The other to use marshalling like you were but explicitly looping through the array.

    static byte[] ConvertToByte(A a)
    {
        var bStructSize = Marshal.SizeOf<B>();
        var size = bStructSize * a.b.Length;
    
        var arr = new byte[size + 4];
    
        var ptr = Marshal.AllocHGlobal(size);
    
        for (int i = 0; i < a.b.Length; i++)
            Marshal.StructureToPtr(a.b[i], ptr + i * bStructSize, true);
    
        Marshal.Copy(ptr, arr, 4, size);
    
        Array.Copy(BitConverter.GetBytes(a.sizeB), arr, 4);
    
        return arr;
    }