GCHandle.FromIntPointer 无法按预期工作

本文关键字:工作 FromIntPointer GCHandle | 更新日期: 2023-09-27 18:20:07

这是一个非常简单(完整(的程序,用于练习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 上的 Mono 中尝试了完全相同的程序 (v2.10.8.1(。 我得到了一个信息量稍大但仍然令人费解的例外GCHandle value belongs to a different domain.

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

GCHandle.FromIntPointer 无法按预期工作

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

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

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

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

反馈报告提交于 connect.microsoft.com

AddrOfPinnedObject不是

FromIntPtr的对立面。您需要ToIntPtr

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

FromIntPtr不获取对象的地址,它采用一个不透明值(恰好定义为 IntPtr(,该值用于检索带有 ToIntPtr 的对象。