Search code examples
c#pointersunsafereinterpret-caststackalloc

Reinterpret C# raw buffer into a blittable structure


I'm looking for a way to reinterpret an arbitrary unsafe memory region as a blittable structure in C#. Here is a failed attempt I could write so far:

[StructLayout(LayoutKind.Explicit, Size = sizeof(int))]
struct Foo
{
    [FieldOffset(0)]
    public int X;
}

public static unsafe void Main()
{
    // Allocate the buffer
    byte* buffer = stackalloc byte[sizeof(Foo)];

    // A tentative of reinterpreting the buffer as the struct
    Foo foo1 = Unsafe.AsRef<Foo>(buffer);

    // Another tentative to reinterpret as the struct
    Foo foo2 = *(Foo*) buffer;

    // Get back the address of the struct
    void* p1 = &foo1;
    void* p2 = &foo2;

    Console.WriteLine(new IntPtr(buffer).ToString("X"));
    Console.WriteLine(new IntPtr(p1).ToString("X"));
    Console.WriteLine(new IntPtr(p2).ToString("X"));
}

Nevertheless, the printed memory addresses are all different (I expected the same address to be printed). The first attempt uses the Unsafe.AsRef<T>(..) provided by Microsoft, where the description of method says:

Reinterprets the given location as a reference to a value of type T.

I'm not sure why the reinterpret is not properly done here.

Any advice ?


Solution

  • The reinterpreting works as intended, the reason the addresses are different is that these two lines create independent copies of data in two new variables:

    Foo foo1 = Unsafe.AsRef<Foo>(buffer);
    // ...
    Foo foo2 = *(Foo*) buffer;
    

    To avoid the copying, you can declare the variables as ref locals (from C#7 onwards) or pointers:

    byte* buffer = ...
    
    // Option 1, pointers only
    Foo* foo1 = (Foo*)buffer;
    foo1->X = 123;
    
    // Option 2, ref local cast from pointer    
    ref Foo foo2 = ref *(Foo*)buffer;
    foo2.X = 456;
    
    // Option 3, ref local with Unsafe.AsRef
    // Unlike option 2 this also allows reinterpreting non-blittable types,
    // but in most cases that's probably undesirable
    ref Foo foo3 = ref Unsafe.AsRef<Foo>(buffer);
    foo3.X = 789;