这是一个非常简单(完整)的程序,用于练习 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);
}
}
}
请注意,此程序本质上是对 CLR via C# (4e) 上用英语描述的程序的音译。在第 547 页。但是,运行它会导致非托管异常,例如:
附加信息:运行时遇到 fatal error 。错误地址位于线程 0x21bc 上的 0x210bc39b。错误代码是 0xc0000005。此错误可能是 CLR 中的错误,或者是用户代码的不安全或不可验证部分中的错误。此错误的常见来源包括 COM 互操作或 PInvoke 的用户编码错误,这可能会损坏堆栈。
认为这可能是 .NET 4.5 中的错误,并且由于我没有发现任何明显的错误,所以我在 Linux 上的 Mono (v2.10.8.1) 中尝试了完全相同的程序。我得到了稍微多一些但仍然令人费解的异常 GCHandle value belongs to a different domain.
据我所知,handle
确实与我调用 GCHandle.FromIntPtr
的代码属于同一个 AppDomain。但事实上我在这两个实现中都看到了异常,这让我怀疑我遗漏了一些重要的细节。这是怎么回事?
最佳答案
您的心智模型有误。 FromIntPtr() 只能转换回您从 ToIntPtr() 获得的值。它们是方便的方法,特别方便在非托管代码中存储对托管对象的引用(并使其保持事件状态)。 gcroot<> template class依赖于它,用于 C++ 项目。这很方便,因为非托管代码只需要存储指针。
底层值,实际指针,称为“句柄”,但它实际上是指向垃圾收集器维护的表的指针。除了垃圾收集器找到的对象之外,该表还创建了对对象的额外 引用。从本质上讲,即使程序不再具有对该对象的有效引用,也允许托管对象继续存在。
GCHandle.AddrOfPinnedObject() 返回一个完全不同的指针,它指向实际的托管对象,而不是“句柄”。 “属于不同的域”异常消息是可以理解的,因为我提到的表与 AppDomain 相关联。
.NET 4.5 中的崩溃很像是一个错误。它确实使用名为 MarshalNative::GCHandleInternalCheckDomain() 的内部 CLR 函数执行测试。 CLR 的 v2 版本引发 ArgumentException,消息文本为“无法跨 AppDomains 传递 GCHandle。”。但是 v4 版本在此方法中崩溃,进而生成 ExecutionEngineException。这看起来不是是故意的。
反馈报告归档于 connect.microsoft.com
关于c# - GCHandle.FromIntPointer 没有按预期工作,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17331987/