GCHandle.FromIntPointer无法按预期工作

时间:2013-06-26 23:14:03

标签: c# mono clr .net-4.5

这是一个非常简单(完整)的程序,用于练习使用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);
        }
    }
}

请注意,此程序基本上是对第547页的CLR via C# (4e)上的英语描述的过程的音译。但是,运行它会导致非托管异常,例如:

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.

认为这可能是.NET 4.5中的一个错误,而且由于我没有看到任何明显错误,我在Linux上的Mono中尝试了完全相同的程序(v2.10.8.1)。我得到了更多信息但仍然令人费解的例外GCHandle value belongs to a different domain.

据我所知,handle确实属于与我调用GCHandle.FromIntPtr的代码相同的AppDomain。但是我在两种实现中都看到异常的事实让我怀疑我错过了一些重要的细节。这是怎么回事?

2 个答案:

答案 0 :(得分:7)

你的心理模型错了。 FromIntPtr()只能转换从ToIntPtr()获得的值。它们是便捷方法,特别是在非托管代码中存储对托管对象的引用(并使其保持活动状态)。 gcroot<> template class依赖于它,在C ++项目中使用。这很方便,因为非托管代码只需要存储指针。

底层值,实际指针,称为“句柄”,但它实际上是指向垃圾收集器维护的表的指针。除了垃圾收集器找到的对象之外,该表还创建了对象的额外引用。本质上允许托管对象存活,即使程序不再具有对该对象的有效引用。

GCHandle.AddrOfPinnedObject()返回一个完全不同的指针,它指向实际的托管对象,而不是“句柄”。 “属于不同的域”异常消息是可以理解的,因为我提到的表与AppDomain相关联。

.NET 4.5中的崩溃看起来像一个bug。它使用名为MarshalNative :: GCHandleInternalCheckDomain()的内部CLR函数执行测试。 CLR的v2版本引发ArgumentException,消息文本“无法通过AppDomains传递GCHandle”。但是v4版本在这个方法中崩溃,而这个方法又生成了ExecutionEngineException。这看起来是故意的。

connect.microsoft.com

提交的反馈报告

答案 1 :(得分:5)

AddrOfPinnedObjectFromIntPtr不同。您想要ToIntPtr代替:

IntPtr pointer = handle.ToIntPtr ();
GCHandle handle2 = GCHandle.FromIntPtr (pointer);

FromIntPtr不接受对象的地址,它采用不透明值(恰好定义为IntPtr),用于检索ToIntPtr的对象。