4

这是一个非常简单(完整)的程序,用于练习 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 页通过 C# (4e) 在 CLR 上用英语描述的过程的音译。但是,运行它会导致非托管异常,例如:

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 (v2.10.8.1) 上的 Mono 中尝试了完全相同的程序。我得到了更多信息但仍然令人费解的例外GCHandle value belongs to a different domain.

据我所知,handle确实与我调用的代码属于同一个 AppDomain GCHandle.FromIntPtr。但是,我在两种实现中都看到了异常,这让我怀疑我遗漏了一些重要的细节。这里发生了什么?

4

2 回答 2

7

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

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

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

.NET 4.5 中的崩溃看起来很像一个错误。它确实使用称为 MarshalNative::GCHandleInternalCheckDomain() 的内部 CLR 函数执行测试。CLR 的 v2 版本引发 ArgumentException,消息文本为“无法跨 AppDomain 传递 GCHandle。”。但是 v4 版本在此方法中崩溃,进而生成 ExecutionEngineException。这看起来不是故意的。

在connect.microsoft.com提交的反馈报告

于 2013-06-27T00:16:47.977 回答
6

AddrOfPinnedObject不是相反的FromIntPtr。你想要ToIntPtr

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

FromIntPtr不取对象的地址,它取一个不透明的值(恰好被定义为 IntPtr),用于用ToIntPtr.

于 2013-06-26T23:45:45.257 回答