Is a Span<T> pointing to Fixed Sized Buffers without a fixed expression possible?
Asked Answered
C

1

10

I'm using .NET Core 2.1 and language standard 7.3. I wish to reference a fixed buffer without obtaining a pointer to it. Is it currently possible?

public unsafe struct InteropStruct
{
    private fixed byte dataField[32];

    public Span<byte> Data
    {
        get
        {
            //return a span referencing the private field without a fixed statement
        }
    }
}

I know Span is currently able to track a managed array through garbage collections, so I don't see anything preventing it from tracking fixed buffers in a similar fashion.

If its not possible, what would happen if I did use a fixed statement like such:

public unsafe struct InteropStruct
{
    private fixed byte dataField[32];

    public Span<byte> Data
    {
        get
        {
            fixed (byte* ptr = dataField)
            {
                return new Span<byte>(ptr, 32);
            }
        }
    }
}

Would the garbage collector become a problem if the struct is wrapped inside an object or class on the heap?

Chalkstone answered 21/1, 2019 at 8:23 Comment(0)
C
7

So, I've done some research in the form of ILSpy'ing .NET Assemblies, and some testing on .NET Core 2.1. My Test results are as follows:

    interface ITest
    {
        Span<byte> Data { get; }
    }

    unsafe struct TestStruct : ITest
    {
        fixed byte dataField[8];

        public Span<byte> Data
        {
            get
            {
                //Unsafe.AsPointer() to avoid the fixed expression :-)
                return new Span<byte>(Unsafe.AsPointer(ref dataField[0]), 8);
            }
        }
    }

    class Program
    {
        //Note: This test is done in Debug mode to make sure the string allocation isn't ommited
        static void Main(string[] args)
        {
            new string('c', 200);

            //Boxes the struct onto the heap.
            //The object is allocated after the string to ensure it will be moved during GC compacting
            ITest HeapAlloc = new TestStruct();

            Span<byte> span1, span2;

            span1 = HeapAlloc.Data; //Creates span to old location

            GC.Collect(2, GCCollectionMode.Forced, true, true); //Force a compacting garbage collection

            span2 = HeapAlloc.Data; //Creates span to new location

            //Ensures that if the pointer to span1 wasn't updated, that there wouldn't be heap corruption
            //Write to Span2
            span2[0] = 5;
            //Read from Span1
            Console.WriteLine(span1[0] == 5); //Prints true in .NET Core 2.1, span1's pointer is updated
        }
    }

What I've learned from my research into the IL, please forgive me if I'm not explaining this correctly:

.NET Core's 2 Field Span:

//Note, this is not the complete declaration, just the fields
public ref readonly struct Span<T>
{
    internal readonly ByReference<T> _pointer;
    private readonly int _length;
}

.NET Framework's 3 Field Span:

//Same note as 2 Field Span
public ref readonly struct Span<T>
{
    private readonly Pinnable<T> _pinnable;
    private readonly IntPtr _byteOffset;
    private readonly int _length;
}

.Net Core is using the 2 field model of Span. Due to the .NET Framework using the 3 field model, It's pointer will not be updated. The reason? The Span<T>(void* pointer, int length) constructor (which I am using for this) for the 3 field span sets the _byteOffset field with the pointer argument. The pointer in the 3 field span that would be updated by the GC is the _pinnable field. With the 2 field Span, they are the same.

So, the answer to my question is, yes I can have a Span point to a fixed buffer with or without a fixed statement, but its dangerous to do this at all when not using .NET Core's 2 field Span Model. Correct me if I'm wrong about .NET Framework's current Span model.

Chalkstone answered 23/1, 2019 at 19:12 Comment(5)
There's absolutely no need to use ILSpy. .NET is open-source (even .NET Framework has its source publicly available) just use source.dot.netLovel
This was back before I knew about source.dot.net. And before I knew about Span<T> MemoryMarshal.CreateSpan<T>(ref T reference, int length)Chalkstone
@Chalkstone how exactly do you use this?Jaal
@Jaal What do you mean? The CreateSpan<T> method I spoke about above? Or...?Chalkstone
@Chalkstone I believe you can't get a ref T from a fixed size buffer.Jaal

© 2022 - 2024 — McMap. All rights reserved.