GCHandle.FromIntPointer does not work as expected

2019-05-06 13:47发布

问题:

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?

回答1:

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.



回答2:

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