Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can't GCHandle.Alloc pin objects containing references?

I suspect that the reason this function doesn't exist is that implementing it is complex and that few people need it. To be safe, you'd want pinning to work transitively, i.e., you'd want the entire graph of reachable objects to be pinned. But it doesn't seem like something that fundamentally can't be done.

E.g., suppose you have the following class:

[StructLayout(LayoutKind.Sequential)]
class SomeObject
{
    public SomeObject r;
}

which you allocate like:

SomeObject o = new SomeObject();

and you try to pin it with:

GCHandle oh = GCHandle.Alloc(o, GCHandleType.Pinned);

you'll get the dreaded:

Object contains non-primitive or non-blittable data.

OK, fine, I can live with that. But suppose I had access to .NET's garbage collector implementation. What would be the obstacles? Here are the obstacles I see:

  1. Circular references.
  2. You want the garbage collector to limit itself to objects inside the app's heap(s).
  3. It could take a long time.
  4. It would be hard/painful to make the operation atomic.

It seems to me that the GC already has to deal with some of these issues. So what am I forgetting?

NOTE: Before you ask "What are you trying to accomplish?", etc., the purpose of my asking is for research code, not necessarily limited to C#, and not necessarily limited to the CLR. I understand that fiddling with the runtime's own memory is not a typical scenario. In any case, this isn't a purely speculative question.

NOTE 2: Also, I don't care about marshaling. I'm just concerned about pinning.

like image 804
Dan Barowy Avatar asked Jun 19 '13 00:06

Dan Barowy


1 Answers

The GC just knows that whatever you are going to next isn't going to work. You pin memory for a reason, surely it is to obtain a stable IntPtr to the object. Which you then next, say, pass to unmanaged code.

There is however a problem with the content of the pointed-to memory. It contains a pointer to a managed object. That pointer is going to randomly change whenever another thread allocates memory and triggers a collection. Which will play havoc on any code that uses the pinned memory content. There is no way to obtain a stable pointer, you can't "freeze" the collector. Pinning the pointer doesn't work either, it just passes the buck to the next pointed-to object. Hopefully it becomes null sooner or later, it would have to, but GC.Alloc doesn't travel the entire dependency graph to check that, there's no decent upper-bound on how long that can take. Getting an entire generation pinned is possible, that's a very hard deadlock.

Ugly problems, it was just much simpler to forbid it. Not much of a real problem, pinvoke happens everyday anyway.

like image 108
Hans Passant Avatar answered Oct 13 '22 09:10

Hans Passant