Must FieldOffset be used on every class/struct member if it is used at all?

690 Views Asked by At

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.

1

There are 1 best solutions below

1
Marc Gravell On

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;
}