0

I posted a related question a few weeks ago : Marshal.Sizeof() returning unexpected value

As background, we hired a third party to convert an old C++ project to C#. This is a communications protocol application that sends/receives messages over ethernet, where all messages contain payloads that are serialized representations of defined structures:

typedef struct      // size=10
{
    ushort group;
    ushort line;
    ushort v_group; 
    byte ip_address[4];
}GROUP_T;

typedef struct      // size=91
{
    byte struct_version;
    ushort region_id;
    byte address[8];
    GROUP_T groups[8];
} LCT_T;

These were converted to C# classes:

[Serializable]
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public class GROUP_T
{
        public ushort group;
        public ushort line;
        public ushort v_group;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4, ArraySubType = UnmanagedType.U1)]
        public byte[] ip_address = new byte[4];
}

[Serializable]
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public class LCT_T
{
        public byte struct_version;
        public ushort region_id;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
        public byte[] address = new byte[8];
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 80)]
        public byte[] group_config_bytes = new byte[80];
}

My problem is with the group_config_bytes element of the LCT_T class. Programmatically this works, but the original array of GROUP_T structs was lost to the equivalent byte array (originally, the GROUP_T array was empty and unused). Now I need to set values for individual GROUP_T objects, so I need the nested-array-of-classes version:

[Serializable]
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public class LCT_T
{
        public byte struct_version;
        public ushort region_id;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
        public byte[] address = new byte[8];
        [MarshalAs(UnmanagedType.ByValArray, ArraySubType=UnmanagedType.Struct,SizeConst = 10)]
        public GROUP_T[] groups = new GROUP_T[8];
}

This compiles, but Marshal.SizeOf(typeof(LCT_T)) is returning the wrong size (should be 11 + (8 * 10) = 91).

Without this updated LCT_T class definition, if I need to set elements of individual groups, I have to poke values directly into group_config_bytes , which is ugly, prone to errors, and unclear to future maintainers of this code.

So: what's the right way to define nested arrays of classes within a class?

buzzard51
  • 1,372
  • 2
  • 23
  • 40
  • So is it `GROUP_T groups[8]` or `new byte[80]`? – GSerg Sep 11 '20 at 16:01
  • currently new byte[80]. I need to change it to have the group_t class: GROUP_T groups[8]. – buzzard51 Sep 11 '20 at 16:03
  • Oh I see. Why don't you just have `[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] public GROUP_T[] groups` in `LCT_T` like you should? Convert all these classes to structs and do that. – GSerg Sep 11 '20 at 16:04
  • because when I do, and the new class LCT_T is serialized, the size is wrong. I think it's how I use the marshalling directive before the GROUP_T[] element. – buzzard51 Sep 11 '20 at 16:07
  • [Works for me](https://dotnetfiddle.net/PrhGMi). – GSerg Sep 11 '20 at 16:12
  • Having a struct start with a byte and a ushort is a terrible design (start up the time machine to fix that). It messed up the alignment of everything that follows (the ushort will be aligned on an odd byte, as will everything that follows). Are you sure you are getting 91 on the unmanaged side? What are you getting from the structure above; I'll bet it's an even number, likely divisible by 4. You may need `LayoutKind.Explicit` to get something do unaligned layed out the way you say you want it – Flydog57 Sep 11 '20 at 16:19
  • @Flydog57 The `Pack = 1` makes it 91. Removing it makes it 92. – GSerg Sep 11 '20 at 16:28
  • @MikeJ: This is a huge project, done by long-gone third parties, that I am tasked to make enhancements to. Why they used classes vs structs, I don't know - but I try to make minimal changes to their design because of ripple effects. I will change these two small classes (simplified for this post) to structs and see if anything downstream breaks - thanks for this answer. – buzzard51 Sep 11 '20 at 16:32
  • If they're serializing the classes over the wire, here's something to consider about class sizes: https://stackoverflow.com/questions/3694423/size-of-a-class-object-in-net If you can't use structs, you'll probably want to implement custom serialization – MikeJ Sep 11 '20 at 16:37

1 Answers1

2

As @GSerg said, you should us structs. The classes have other things associated with them. And I think as he also said the marshaling directive is causing the wrong size. Here's a better example, extracting the Groups out so it's more obivous.

[Serializable]
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct GROUP_T
{
    public static GROUP_T Default = new GROUP_T() { ip_address = new byte[4] };

    public ushort group;
    public ushort line;
    public ushort v_group;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4, ArraySubType = UnmanagedType.U1)]
    public byte[] ip_address;
}

[Serializable]
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct LCT_T
{
    public byte struct_version;
    public ushort region_id;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
    public byte[] address;
    
}

public struct LCT_T_WITH_GROUP 
{
    public LCT_T lct;
    [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.Struct, SizeConst = 8)]
    public GROUP_T[] groups;
}

class Program
{
    static void Main(string[] _)
    {
        int sizeOfGroup = Marshal.SizeOf(typeof(GROUP_T));
        int sizeOfStruct = Marshal.SizeOf(typeof(LCT_T));
        int sizeOfLctWithGroup = Marshal.SizeOf(typeof(LCT_T_WITH_GROUP));

        Console.WriteLine($"GROUP_T: {sizeOfGroup}");
        Console.WriteLine($"LCT_T: {sizeOfStruct}");
        Console.WriteLine($"LCT_T_WITH_GROUP: {sizeOfLctWithGroup}");
    }
}

Output of this is: GROUP_T: 10 LCT_T: 11 LCT_T_WITH_GROUP: 91

MikeJ
  • 1,299
  • 7
  • 10