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
- Anonymous
September 18, 2004
Wow, I sure wouldn't want to be unlucky. - Anonymous
September 22, 2004
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'? - Anonymous
September 22, 2004
btw I'm using .NET 2.0 beta - Anonymous
October 08, 2006
Both GCHande's ToIntPtr and AddrOrPinnedObject take in a GCHandle and return an IntPtr. I had to do a - Anonymous
August 28, 2007
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 FunctionThe object is a VB 2005 TextBox. Any ideas how to solve this issue? - Anonymous
August 31, 2007
Hi rcw8892Your 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 - Anonymous
September 25, 2008
There are only a few things that can make a .NET process crash.  The most common one is an Unhandled - Anonymous
October 08, 2008
MostcommoncauseofCLRcrashismanagedheapscorruptionwhichoftenoccursinP/Invokeapplicatio... - Anonymous
October 10, 2008
PingBack from http://www.salmonsalvo.net/blog/?p=68 - Anonymous
June 29, 2009
The comment has been removed