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));

然后我看到了崩溃。

那么微软的例子是错误的还是我错过了什么?

4

2 回答 2

7

您错过了使用调试器对局部变量生命周期的影响。在附加调试器的情况下,抖动标记正在使用的变量,直到方法结束。重要的是使调试可靠。然而,这也阻止了 GC.Collect() 调用收集委托对象。

当您在没有调试器的情况下运行程序的发布版本时,此代码将崩溃。

这篇文章中提供了有关 Debug 构建行为对垃圾收集器的影响的深入答案

于 2013-05-14T13:36:18.777 回答
2

'Alloc' 调用添加了对委托的引用,这会阻止 GC 收集它。使用函数指针完成后,您必须保留从 Alloc 返回的句柄并在其上调用 Free()。委托将在没有调用 Alloc 的情况下被 GC。如果你不在 GCHandle 上调用 Free(),程序就会泄漏。在调试器中运行时,内存环境有点不同。说得通?

于 2013-05-14T13:43:15.320 回答