Reputation: 1467
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?
Upvotes: 0
Views: 174
Reputation: 1369
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
Upvotes: 2