从非托管C回调C#很棘手.
我从中学到了大部分所需要的东西
MSDN article
还有这个
stackoverflow tip,
并且结果在调试器中工作正常.
但是在调试器之外它失败了“对象引用没有设置为对象的实例”.
我从中学到了大部分所需要的东西
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减少了托管堆的碎片潜力.“
怎么了?
解决方法
您必须分配委托本身,而不是其IntPtr.完成CSharpCode实例后,您还必须释放GCHandle.
class CSharpCode : IDisposible { delegate void CallbackDelegate(); GCHandle gchCallbackDelegate; void DoCSharp() { CallbackDelegate callbackDelegate = TheCallback; IntPtr callbackDelegatePointer = Marshal.GetFunctionPointerForDelegate(callbackDelegate); gchCallbackDelegate = GCHandle.Alloc(callbackDelegate); // !!!! GC.Collect(); // create max space for unmanaged allocations CppCliCode.DoCppCli(callbackDelegatePointer); } public void Dispose() { CleanUp(); } ~CSharpCode() { CleanUp(); } CleanUp() { if(gchCallbackDelegate.IsAllocated) gchCallbackDelegate.Free(); } }
顺便说一下,我希望你有更强大的命名系统. DoCSharp,TheCallBack,theCallBack等名称让我很难理解这个问题.