Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does .Net runtime "understand" that structure still has a reference

The question is how .Net runtime understands that fields of a structure, that was placed onto memory using Marshal.StructureToPtr, must not be freed by GC.

Below the scenario.

I have the following structure:

[StructLayout(LayoutKind.Sequential)]
public struct SomeStruct
{
    public string s;
    public Stream stream;

    public SomeStruct(string s)
    {
        this.s = s;
        this.stream = new MemoryStream(0x100);
    }
}

There is a method that instantiates the structure and drops it to memory:

static IntPtr GetStructRawData()
{
    IntPtr ptr = Marshal.AllocHGlobal(1024);
    Marshal.StructureToPtr(new SomeStruct("hi"), ptr, false);
    return ptr;
}

Then I can make new structure from the raw memory:

IntPtr ptr = GetStructRawData();

GC.Collect();

SomeStruct struct2 = (SomeStruct)Marshal.PtrToStructure(ptr, typeof(SomeStruct));

Aftet that struct2 really contains correct string ("hi"), and correct stream. So it seems that there are references to that string and to that stream of struct1. But what holds the references? How does the runtime understands that the string and the stream must not be collected?

like image 344
Artem Razin Avatar asked Apr 26 '26 01:04

Artem Razin


1 Answers

But what holds the references? How does the runtime understands that the string and the stream must not be collected?

The string here is a bit of a special case; it is actually an interned string (loaded via ldstr), so it is already rooted by the intern table.

The MemoryStream however... frankly, it isn't rooted. Your code is inherently broken and dangerous, and it could fail horribly at any time. The objects could be collected or moved (compaction) at any time, and that would leave broken references in the unmanaged memory, because the GC isn't looking at the unmanaged memory.

I believe your code is only "working" currently because the GC hasn't been aggressive with you. Also keep in mind: GC doesn't erase objects; if the GC just decided to consider the MemoryStream as collected, you might still be able to talk to it again without it complaining, if the memory still looks OK for a while. But this is just working for the wrong reasons.

Having references in unmanaged memory is a terrible terrible idea and will hurt you.

If you're going to use unmanaged memory, the where T : unmanaged constraint could be a life-saver for you. It prevents you getting into this scenario, but as a necessity restricts what you can do. Meaning: you can't have these fields.

like image 118
Marc Gravell Avatar answered Apr 27 '26 16:04

Marc Gravell



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!