Here's a very simple (complete) program for exercising the use of GCHandle.FromIntPointer:
using System;
using System.Runtime.InteropServices;
namespace GCHandleBugTest
{
class Program
{
static void Main(string[] args)
{
int[] arr = new int[10];
GCHandle handle = GCHandle.Alloc(arr, GCHandleType.Pinned);
IntPtr pointer = handle.AddrOfPinnedObject();
GCHandle handle2 = GCHandle.FromIntPtr(pointer);
}
}
}
Note that this program is essentially a transliteration of the procedure described in English on CLR via C# (4e) on page 547. Running it, however, results in an unmanaged exception like:
Additional Information: The runtime has encountered a fatal error. The address of the error was at 0x210bc39b, on thread 0x21bc. The error code is 0xc0000005. This error may be a bug in the CLR or in the unsafe or non-verifiable portions of user code. Common sources of this bug include user marshaling errors for COM-interop or PInvoke, which may corrupt the stack.
Thinking that this might be a bug in .NET 4.5, and since I don't see anything obviously wrong, I tried exactly the same program in Mono on Linux (v2.10.8.1). I got the slightly more informative but still puzzling exception GCHandle value belongs to a different domain.
As far as I am aware, the handle
really does belong to the same AppDomain as the code where I call GCHandle.FromIntPtr
. But the fact that I see an exception in both implementations makes me suspect that I am missing some important detail. What's going on here?
AddrOfPinnedObject
is not the opposite of FromIntPtr
. You want ToIntPtr
instead:
IntPtr pointer = handle.ToIntPtr ();
GCHandle handle2 = GCHandle.FromIntPtr (pointer);
FromIntPtr
does not take the address of the object, it takes an opaque value (which happens to be defined as IntPtr), which is used to retrieve the object with ToIntPtr
.
You've got the wrong mental model. FromIntPtr() can only convert back the value you got from ToIntPtr(). They are convenience methods, handy in particular to store a reference to a managed object (and keep it alive) in unmanaged code. The gcroot<> template class relies on it, used in C++ projects. It is convenient because the unmanaged code only has to store the pointer.
The underlying value, the actual pointer, is called a "handle" but it is really a pointer into a table that the garbage collector maintains. The table create extra references to objects, in addition to the ones that the garbage collector finds. In essence allowing a managed object to survive even though the program no longer has a valid reference to the object.
GCHandle.AddrOfPinnedObject() returns a completely different pointer, it points to the actual managed object, not the "handle". The "belongs to a different domain" exception message is understandable since the table I mentioned is associated with an AppDomain.
The crash in .NET 4.5 strongly looks like a bug. It does perform a test with an internal CLR function called MarshalNative::GCHandleInternalCheckDomain(). The v2 version of the CLR raises an ArgumentException with the message text "Cannot pass a GCHandle across AppDomains.". But the v4 version crashes inside this method which in turn generates the ExecutionEngineException. This does not look intentional.
Feedback report filed at connect.microsoft.com