【发布时间】:2011-12-14 05:21:39
【问题描述】:
从非托管 C++ 回调到 C# 很棘手。 我从中学到了大部分必需的东西 MSDN article 和这个 stackoverflow tip, 结果在调试器中运行良好。 但在调试器之外,它会因“对象引用未设置为对象的实例”而失败。
这是(简化的)C# 代码:
class CSharpCode
{
delegate void CallbackDelegate();
void DoCSharp()
{
CallbackDelegate callbackDelegate = TheCallback;
IntPtr callbackDelegatePointer = Marshal.GetFunctionPointerForDelegate(callbackDelegate);
GCHandle gchCallbackDelegate = GCHandle.Alloc(callbackDelegatePointer);
GC.Collect(); // create max space for unmanaged allocations
CppCliCode.DoCppCli(callbackDelegatePointer);
}
public static void TheCallback()
{
MessageBox.Show("It worked");
}
}
这是 C++ 代码:
#pragma managed
public ref class CppCliCode
{
static void DoCppCli(IntPtr^ callbackDelegatePointer)
{
callback theCallback = static_cast<callback>(callbackDelegatePointer->ToPointer());
DoCpp(theCallback);
}
}
#pragma unmanaged
typedef void (__stdcall *callback)();
void DoCpp(callback theCallback)
{
theCallback();
}
错误发生在调用theCallback() 和到达TheCallback() 之间。该错误表明某些不可见的托管对象已变为null。
如果我删除 GC.Collect() 问题就会消失。但这只是意味着有一天,当 GC 恰好发生在错误的时刻时,它会作为一个间歇性的谜团重新出现。
GCHandle 保护委托不被收集,但允许它被重新定位。 MSDN文章说“如果一个委托被垃圾回收重新定位,它不会影响底层托管回调,因此Alloc用于添加对委托的引用,允许重新定位委托,但阻止处置。使用GCHandle而不是 pin_ptr 减少了托管堆的碎片可能性。”
怎么了?
【问题讨论】:
-
您的代码可能违反了这条规则:您必须手动阻止垃圾收集器从托管代码中收集委托 (msdn.microsoft.com/en-us/library/…)。尝试在 C# 中将 callbackDelegate 变量定义为类成员。
-
GCHandle 使错误的对象保持活动状态。如果你做对了,你就会有泄漏。而是在方法的 end 处添加 GC.KeepAlive(callbackDelegate)。调试器做同样的事情。
-
@HansPassant 你为什么不把这个作为答案发布?
-
因为解释如何正确地做到这一点需要更多的工作。现在没时间。
-
GC.KeepAlive(callbackDelegate) 确实解决了这个问题!如果@HansPassant 想发布答案,我会接受。