2

Consider the case where I need to ensure a class/struct is mapped to memory in a very specific way, probably due to the need to match an external protocol:

[StructLayout(LayoutKind.Sequential, Pack=1)]
public class SYSTEM_INFO
{
 public ulong OemId;
 public ulong PageSize;
 public ulong ActiveProcessorMask;
 public ulong NumberOfProcessors;
 public ulong ProcessorType;
}

Then I thought about doing an 'overlay' (is this a proper term?) so I can directly access the memory:

[StructLayout(LayoutKind.Explicit)]
public class SYSTEM_INFO
{
[FieldOffset(0)] public byte[] Buffer = new byte[40]; //overlays all the bytes, like a C union
[FieldOffset(0)] public ulong OemId;
[FieldOffset(8)] public ulong PageSize;
[FieldOffset(16)] public ulong ActiveProcessorMask;
[FieldOffset(24)] public ulong NumberOfProcessors;
[FieldOffset(32)] public ulong ProcessorType;
}

But this gets time-consuming and error-prone (if something changes I could easily mess up updating all the FieldOffset values) - and has been pointed out is actually not valid for reasons I do not fully understand:

Unhandled exception. System.TypeLoadException: Could not load type 'SYSTEM_INFO2' from assembly 'a2bbzf3y.exe, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' because it contains an object field at offset 0 that is incorrectly aligned or overlapped by a non-object field. Command terminated by signal 6

Is it possible to do a combination of both approaches, where Buffer overlays the members, but all members except Buffer are automatically, sequentially aligned without padding? I cannot make out from the docs if this is allowed or not, and I can force some member offsets only with others automatically decided.

Mr. Boy
  • 60,845
  • 93
  • 320
  • 589
  • Overlay is the correct terminology. I think what will work if you have a base class and a class that inherits the base class you can create an overlay. To have FieldOffset will only work with LayoutKind.Explicit and then each property must have the FieldOffset. – jdweng Jan 27 '20 at 16:37
  • That `[40]` doesn't compile (C# 7.3). I *think* you need `[MarshalAs(UnmanagedType.ByValArray, SizeConst = 40)]`: `[FieldOffset(0), MarshalAs(UnmanagedType.ByValArray, SizeConst = 40)] public byte[] Buffer;` I am unsure about that first parameter though. –  Jan 27 '20 at 16:46
  • @Amy I *believe* they're more intending it to be a "fixed buffer" that happens to span the entire object: `public fixed byte Buffer[40];` – Marc Gravell Jan 27 '20 at 16:51
  • @MarcGravell I defer to your wisdom here, my experience with marshalling is pretty weak, to be honest. Should I delete my comment? –  Jan 27 '20 at 16:53
  • @Amy I wouldn't (delete it), because I'm pretty sure other readers would have the same thought, so it helps to retain that context / clarification – Marc Gravell Jan 27 '20 at 16:54

1 Answers1

2

In the case of a struct, you can do this quite easly - and frankly, if you're looking at the bytes, you should generally be using a struct:

using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

static class P
{
    static void Main()
    {
        var obj = new SystemInfo();
        var bytes = AsBytes(ref obj);
        Console.WriteLine(bytes.Length); // 40
    }

    static Span<byte> AsBytes<T>(ref T value)
        => MemoryMarshal.CreateSpan(
            ref Unsafe.As<T, byte>(ref value),
            Unsafe.SizeOf<T>());
}

public readonly struct SystemInfo
{
    public readonly ulong OemId;
    public readonly ulong PageSize;
    public readonly ulong ActiveProcessorMask;
    public readonly ulong NumberOfProcessors;
    public readonly ulong ProcessorType;
}
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • It's an interesting approach but seems to circumvent rather than directly answer the question in favour of a different approach. It is welcome but I'm still curious in a more direct answer - this is a whole side of C# I've never worked with – Mr. Boy Jan 27 '20 at 17:03