4

GCHandle.Alloc“保护对象不被垃圾收集”,但仅仅在静态变量中保存对该对象的引用也会阻止它被收集。提供什么好处GCHandle.Alloc(假设GCHandleType.Normal)?

这篇文章说代表“不需要固定在任何特定的内存位置”,但我在 MSDN 上找不到任何文档来支持该声明。如果委托被 CLR 垃圾收集器移动,那么 umanaged 库如何找到它以便调用它?

请注意,不能固定代表;你会得到一个异常,说明“对象包含非原始或非 blittable 数据”。

4

1 回答 1

11

托管对象通常由垃圾收集器通过遍历 AppDomain 静态和正在运行的线程的堆栈以及它们在 GC 堆上引用的对象来发现。但是在某些情况下,收集器本身无法找到对活动对象的引用,并且不应被收集。

当非托管代码使用此类对象时,就会发生这种情况。这段代码没有被jit,所以GC没有很好的方法来发现对象引用,不能可靠地检查这些代码的堆栈帧来找到返回的指针。您必须确保 GC 仍能看到引用。这就是 GCHandle 所做的。它使用内置在 CLR 中的专用 GC 句柄表。您使用 GCHandle.Alloc() 将条目分配到此表中,稍后使用 GCHandle.Free() 再次释放它。垃圾收集器只是将这个表中的条目添加到它在执行收集时自己发现的对象图中。

C++/CLI 中的 gcroot<> 关键字就是一个例子。它允许编写可以存储简单原始指针并在需要时将其转换回托管对象引用的非托管代码。GCHandle.ToIntPtr() 方法生成该指针,FromIntPtr() 恢复对象引用。GCHandle 表条目确保在非托管代码显式调用 Free() 之前不会收集对象。通常由 C++ 析构函数完成。

GCHandle 还支持固定对象的功能,当您需要编组为本机代码并且 pinvoke 编组器无法完成工作时使用。您将传递 GCHandle.AddrOfPinnedObject() 的返回值。它实现了弱引用,尽管您通常总是为此使用 Wea​​kReference 类。它还实现了异步固定句柄,允许在 I/O 完成时自动取消固定固定 I/O 缓冲区,这是一种不直接公开的功能。所以是的,GCHandle 不仅仅是保留参考。

对于您关于委托的问题很重要,CLR 支持使用委托来调用本机代码。底层辅助函数是 Marshal.GetFunctionPointerForDelegate()。它动态构建了一个机器代码存根,允许本机代码回调到托管代码中。这需要委托对象保持引用,GCHandle.Alloc() 经常用于此目的。这不是唯一的方法,将委托存储到静态变量中是另一种确保委托对象不会被垃圾收集的方法。否则不必固定该委托, GCHandleType.Normal 就可以了。

这在任何 .NET 程序中都被大量使用,但大部分都看不见。特别是在 .NET 框架代码本身和 pinvoke 编组器中。仅当本机代码存储函数指针并可以稍后进行回调时,才需要使用 GCHandle 来保护委托对象。

于 2013-08-08T09:36:16.757 回答