1

There is a code using the StructLayout attribute with generic as below.

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public class Packet<H, Body1, Body2> : IOutlinePacket
    where H : new()
    where Body1 : IDeviceBody, new()
    where Body2 : INetworkBody, new()
{
    H Header = new Header();
    Body1 DeviceBody = new Body1();
    Body2 NetworkBody = new Body2();
}

If the class is not generic the sequence of H, Body1, Body2 will be guaranteed and will be allocated by a 1byte step. But I don't know how about the generic type case.

Of course, I know the generic type is not supported the marshal function but I want to know how about StructLayout case.

I tried to a few test to check this and according to a tests, it seems to be work but I don't know whether it is a coincidence or it is always work.

I'm sorry for my terrible English and Thank you for reading.

jjw
  • 282
  • 3
  • 20
  • I'm not sure why the compiler would treat a generic class any different. As far as I know it should layout `Packet` the same as if you had declared the same three fields in a non generic class. – JonasH Apr 11 '22 at 12:37
  • `StructLayout` only gives you the unmanaged layout, not the managed layout. Generic types aren't marshalable to unmanaged, so it makes little difference. Eg try `Marshal.SizeOf` https://dotnetfiddle.net/sWssIV What exactly are you trying to achieve by adding `StructLayout`? – Charlieface Apr 11 '22 at 12:48
  • There are many packets of a similar format. So I try to create system that can create new packet types combining many other types. for easy to expansion. – jjw Apr 11 '22 at 13:01
  • *"I try to create system that can create new packet types combining many other types."* I can't see how `StructLayout` is relevant at all to that. – Charlieface Apr 11 '22 at 13:33
  • I think it needs a new article to explain this. I'm sorry for I can't explain detail. – jjw Apr 12 '22 at 02:27

1 Answers1

1

This will work as expected. If you add StructLayout which specifies a packing to a generic class, it will add a .pack directive to the IL generated.

For example, given this class:

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public class Test<T1, T2> where T1: struct where T2: struct
{
    public T1 One;
    public T2 Two;
}

The IL output for this is something like this:

.class public sequential ansi beforefieldinit Test`2<valuetype .ctor ([System.Runtime]System.ValueType) T1, valuetype .ctor ([System.Runtime]System.ValueType) T2>
    extends [System.Runtime]System.Object
{
    .pack 1
    .size 0

    .field public !T1 One

    .field public !T2 Two

    .method public hidebysig specialname rtspecialname instance void .ctor () cil managed 
    {
        IL_0000: ldarg.0
        IL_0001: call instance void [System.Runtime]System.Object::.ctor()
        IL_0006: ret
    }
}

Note the .pack 1, which specifies the packing.

You can verify that this packing does in fact work by writing some unsafe code along these lines:

public static class Program
{
    public static void Main()
    {
        var test = new Test<byte, long>
        {
            One = 1,
            Two = 2
        };

        unsafe
        {
            fixed (byte* p1 = &test.One)
            fixed (long* p2 = &test.Two)
            {
                Console.WriteLine((byte*)p2 - p1);
            }
        }
    }
}

The output of this when using Pack = 1 is 1.

If you change the packing to Pack = 4, the output is 4.

And if you change the packing to Pack = 8, the output is 8.

This demonstrates that the packing in memory is performed, even without using marshalling.

There are many packets of a similar format. So I try to create system that can create new packet types combining many other types. for easy to expansion.

I'm not totally sure what you mean by this, but it could be that "discriminated unions" might be of use to you in the future - but it doesn't look like they will be implemented any time soon, so possibly it will be years before they are available (if ever).

Also note that this only works with blittable types!

See here for the documentation

Matthew Watson
  • 104,400
  • 10
  • 158
  • 276
  • Thank you very much. This is the answer I wanted. Does the sequential guaranteed? – jjw Apr 11 '22 at 13:21
  • 1
    I believe it is guaranteed, but it is hard to find the explicit documentation for it! The best I could find is https://studylib.net/doc/9043849/the-il-assembly-language-programmers--reference – Matthew Watson Apr 11 '22 at 13:24
  • Entirely undocumented: the arrangement of the managed version is not guaranteed at all. Note that this all only works with blittable types. That it works is entirely down to internal details of the virtual machine layout. This might not work on say Mono or another implementation, or a different version of .NET. – Charlieface Apr 11 '22 at 13:30
  • @Charlieface Aye, this does only work with blittable types; I'll ad that clarification. – Matthew Watson Apr 11 '22 at 13:33
  • @Charlieface Although [the documentation](https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.structlayoutattribute.pack?view=net-6.0) does actually say `Occasionally, the field is used to reduce memory requirements by producing a tighter packing size. However, this usage requires careful consideration of actual hardware constraints, and may actually degrade performance.` which strongly implies that it is in fact defined. – Matthew Watson Apr 11 '22 at 13:38
  • @Charlieface And furthermore, the examples in the documentation are doing exactly the same kind of pointer arithmetic as in my example, so I'm going to go ahead and say it IS defined. – Matthew Watson Apr 11 '22 at 13:40