GCHandles, Boxing and Heap Corruption


A GCHandle is a struct used to hold onto a managed object to be used by unmanaged code.  With a GCHandle you can (among other things):

  • Prevent an object from being garbage collected if unmanaged code has the only live reference to it
  • Pin an object in memory, so it won’t be relocated in memory by the garbage collector
  • Get the memory address of the pinned object

The last point is interesting.  When dealing with managed code, addresses of reference objects (objects allocated on the heap) are not constant.  As the GC tries to reclaim memory, it moves objects around in memory and compacts the heap.  If you’re P/Invoking into an unmanaged DLL, you may need to pass the address of some object.  That’s where GCHandles come in.

The syntax for allocating a pinned GCHandle is as follows (C# code):

Int[] arr = new int[10];
GCHandle gch = GCHandle.Alloc(arr, GCHandleType.Pinned);

The above code pins arr in memory (so it won’t be moved), and creates a new GCHandle that “wraps” arr.  You can now get the address of arr using GCHandle.AddrOfPinnedObject.

Note: you can only pin blittable types in memory (types that have the same representation in managed and unmanaged code).  Blittable types include primitive types and arrays.

The problem occurs when you accidentally misuse Alloc.  For example, I’ve seen code like this:

Object obj = new Object();
GCHandle gch2 = GCHandle.Alloc(Marshal.SizeOf(typeof(obj)), GCHandleType.Pinned);

It looks like the developer tried to allocate a GCHandle with the same size as obj in memory.  So what does this code actually do?

Marshall.SizeOf returns an int that when passed to a method expecting an Object, is boxed into a newly heap-allocated Object.  So the new GCHandle obediently pins this new Object in memory.  Then when you pass gch2.AddrOfPinnedObject to your unmanaged code… trouble.

Consider an unmanaged method takes an array of ints, and increments each element.  You’ve passed it an address, and it dutifully increments each value it finds for the length of the array.  Congratulations, you’ve just corrupted memory.

If you’re lucky, this crashes the runtime right away.  If you’re unlucky, your app may continue to run and crash sometime in the future or your app’s data may be messed up.

Comments (10)

  1. Dino says:

    Wow, I sure wouldn’t want to be unlucky.

  2. Gyula says:

    Chris, what about invoking non-managed code with ref arguments?

    eg.

    [DllImport("adsapi32")]

    static extern internal int DRV_DeviceOpen(uint card, ref int hndDriver);

    int hnd; // it might be a member of a class

    Adsapi.DRV_DeviceOpen(devNum, ref hnd);

    in this case, should I pin ‘hnd’?

  3. Gyula says:

    btw I’m using .NET 2.0 beta

  4. Both GCHande’s ToIntPtr and AddrOrPinnedObject take in a GCHandle and return an IntPtr. I had to do a

  5. rcw8892 says:

    I am pulling my hair out – I am trying to find a replacement for VB6 ObjPtr. Got some VB6 code I need to convert.  I have tried this but it fails.

      Private Function ObjPtr(ByVal o As Object) As Integer

           Dim GC As GCHandle = GCHandle.Alloc(o, GCHandleType.Pinned)

           Dim ret As Integer = GC.AddrOfPinnedObject.ToInt32

           GC.Free()

           Return ret

       End Function

    The object is a VB 2005 TextBox.  

    Any ideas how to solve this issue?

  6. clyon says:

    Hi rcw8892

    Your ObjPtr function will fail froa  a few reasons.  

    1) o has to be a blittable type (see http://msdn2.microsoft.com/en-us/library/aa904048(VS.71).aspx)

    2) You’re trying to use the address after you’ve freed the handle.  Once you call Free, there’s no guarantee that the address is still valid, since the GC could have moved your object.

    -Chris

  7. There are only a few things that can make a .NET process crash.  The most common one is an Unhandled

  8. Little Snail says:

    MostcommoncauseofCLRcrashismanagedheapscorruptionwhichoftenoccursinP/Invokeapplicatio…

  9. barias@axiscode.com says:

    I am running on Vista x64 SP1 with VS2008 SP1 with .net 3.5 SP1, and the following code crashes the run-time:

       static int Main(string[] args)

       {

           byte[] hash = new byte[20];

           GCHandle gch = GCHandle.Alloc(hash, GCHandleType.Pinned);

           IntPtr MyPtr = gch.AddrOfPinnedObject();

           GCHandle temp = GCHandle.FromIntPtr(MyPtr);  //This line crashes the CLR (the exception cannot be caught!!!!).

           temp.Free();

           return 0;

       }

    The system event log says “.NET Runtime version 2.0.50727.3053 – Fatal Execution Engine Error (000007FEF95CAA6E) (80131506)”

    Why is that happening?