防止委托被垃圾回收

4

我目前正在尝试将一个C#委托封送到一个C++函数指针中,我查看了Microsoft的示例:

// MarshalDelegate1.cpp
// compile with: /clr
#include <iostream>

using namespace System;
using namespace System::Runtime::InteropServices;

#pragma unmanaged

// Declare an unmanaged function type that takes two int arguments
// Note the use of __stdcall for compatibility with managed code
typedef int (__stdcall *ANSWERCB)(int, int);

int TakesCallback(ANSWERCB fp, int n, int m) {
   printf_s("[unmanaged] got callback address, calling it...\n");
   return fp(n, m);
}

#pragma managed

public delegate int GetTheAnswerDelegate(int, int);

int GetNumber(int n, int m) {
   Console::WriteLine("[managed] callback!");
   return n + m;
}

int main() {
   GetTheAnswerDelegate^ fp = gcnew GetTheAnswerDelegate(GetNumber);
   GCHandle gch = GCHandle::Alloc(fp);
   IntPtr ip = Marshal::GetFunctionPointerForDelegate(fp);
   ANSWERCB cb = static_cast<ANSWERCB>(ip.ToPointer());
   Console::WriteLine("[managed] sending delegate as callback...");

// force garbage collection cycle to prove
// that the delegate doesn't get disposed
   GC::Collect();

   int answer = TakesCallback(cb, 243, 257);

// release reference to delegate
   gch.Free();
}

调用 GCHandle::Alloc() 函数旨在防止垃圾回收器收集委托。但是我理解变量 GetTheAnswerDelegate^ fp 已经使得委托保持了生命,因为它是根对象。实际上即使我删除对 GCHandle 的调用,示例仍然可以工作。只有当我像这样内联委托实例化时:
IntPtr ip = Marshal::GetFunctionPointerForDelegate(gcnew GetTheAnswerDelegate(GetNumber));

然后我看到了一个崩溃。

那么,这个来自微软的例子是错的还是我漏掉了什么?

2个回答

7
你忽略了使用调试器对局部变量生命周期的影响。当调试器附加时,JIT编译器会标记在方法结束之前仍在使用的变量。这对于使调试变得可靠非常重要。然而,这也防止GC.Collect()调用收集委托对象。
如果你在没有调试器的情况下运行程序的Release版本,这段代码将崩溃。
有关Debug版本行为对垃圾回收器影响的详细答案,请参见this post

你是绝对正确的!在没有使用GCHandle :: Alloc()的情况下运行发布版本总是会崩溃,即使在调试器中也是如此。谢谢! - user31157

2

“Alloc” 调用会增加委托的引用计数,这可以防止 GC 对其进行回收。您需要保留从 Alloc 返回的句柄,并在使用函数指针后调用 Free()。如果不调用 GCHandle 的 Free(),程序将泄漏。如果没有调用 Alloc,委托将会被 GC 回收。在调试器中运行时,内存环境有所不同。明白了吗?


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接