5

GCHandle.Alloc "protects the object from garbage collection," but merely holding a reference to that object in a static variable will also prevent it from being collected. What benefit does GCHandle.Alloc provide (assuming GCHandleType.Normal)?

This article says that delegates "need not be fixed at any specific memory location" but I can't find any documentation on MSDN to back that statement up. If the delegate is moved by the CLR garbage collector, how can the umanaged library find it so that it can be called?

Note that delegates cannot be pinned; you will get an exception stating "Object contains non-primitive or non-blittable data".

1 Answer 1

12

Managed objects are normally discovered by the garbage collector by walking AppDomain statics and the stacks of the running threads and the objects they reference on the GC heap. But there are certain scenarios where the collector itself is not capable of finding a reference to an object that's live and should not be collected.

This happens when unmanaged code uses such objects. This code is not jitted so the GC has no good way to discover the object references back, the stack frame of such code cannot reliably be inspected to find the pointer back. You must ensure that the GC still sees a reference. Which is what GCHandle does. It uses a dedicated table of GC handles built inside the CLR. You allocate an entry into this table with GCHandle.Alloc() and release it again later with GCHandle.Free(). The garbage collector simply adds the entries in this table to the graph of objects it discovered itself when it performs a collection.

The gcroot<> keyword in C++/CLI is an example of this. It allows writing unmanaged code that can store a simple raw pointer and have it converted back to the managed object reference where needed. The GCHandle.ToIntPtr() method generates that pointer, FromIntPtr() recovers the object reference. The GCHandle table entry ensures the object won't be collected until the unmanaged code explicitly calls Free(). Usually done by a C++ destructor.

GCHandle also supports the ability to pin an object, used when you need to marshal to native code and the pinvoke marshaller can't get the job done. You'd pass the return value of GCHandle.AddrOfPinnedObject(). And it implements weak references, although you'd normally always use the WeakReference class for that. And it implements async pinned handles, allowing pinned I/O buffers to be unpinned automatically on an I/O completion, a capability that's not directly exposed. So yes, GCHandle does more than just keeping a reference.

And significant to your question about delegates, the CLR supports using a delegate to call native code. The underlying helper function is Marshal.GetFunctionPointerForDelegate(). Which dynamically builds a machine code stub that allows native code to callback into managed code. This requires the delegate object to stay referenced, GCHandle.Alloc() is often used for that. It isn't the only way, storing the delegate into a static variable is another way to ensure that the delegate object won't be garbage collected. That delegate doesn't otherwise have to be pinned, a GCHandleType.Normal is fine.

This is used a lot in any .NET program, but most of it is out of sight. Particularly in the .NET framework code itself and in the pinvoke marshaller. Having to use GCHandle to protect a delegate object is only necessary when the native code stores the function pointer and can make callbacks later.

6
  • "That delegate doesn't otherwise have to be pinned" -- Why doesn't it need to be pinned? How can the unmanaged code find it if it's been moved? "Having to use GCHandle to protect a delegate object is only necessary when the native code stores the function pointer and can make callbacks later" -- That sounds inconsistent with the statement "storing the delegate into a static variable is another way to ensure that the delegate object won't be garbage collected," which is it?
    – mpen
    Commented Aug 8, 2013 at 15:37
  • Delegates are like an iceberg, 90% is under water and lives in the CLR. You can study that code by downloading the SSCLI20 source code. Beware that especially the unmanaged interop plumbing for delegates is rough code. There is otherwise no inconsistency, storing a delegate in a static variable simply does the same thing GCHandle does, keeping a reference to the object. Storing it in a member of a class is fine as well, as long as you can ensure that the class object out-lives the ability for the native code to make callbacks. Commented Aug 8, 2013 at 16:33
  • It's inconsistent because you used the word "necessary". It's not necessary if there's an alternative method to keeping the object alive. If such is the case, I will push the delegate into a dictionary (I need it for later use anyway), and forget about GCHandle as it doesn't sound like it's needed at all for this scenario, nor does it appear I need an IntPtr.
    – mpen
    Commented Aug 8, 2013 at 18:15
  • I have no idea what you are unhappy about. Please edit the answer to your liking. Commented Aug 8, 2013 at 18:22
  • 1
    I'm not unhappy, I'm sorry if it came off that way. I do appreciate you taking the time to give a thorough answer. I'm just trying to understand how this all works because it's almost impossible to test and debug, because I can't force a memory move (AFAIK), and I don't know what will happen if an error occurs -- a random crash after several minutes of working perfectly can be hard to track down. I just wanted to clarify a few things is all. My last comment wasn't meant as an 'attack' either, it was more of a question to see if you saw any issues with that approach.
    – mpen
    Commented Aug 8, 2013 at 18:51

Not the answer you're looking for? Browse other questions tagged or ask your own question.