从非托管c++到c#的回调工作,但仅在调试器中
本文关键字:工作 调试器 回调 c++ | 更新日期: 2023-09-27 18:17:48
从非托管c++回调到c#是很棘手的。我从中学到了大部分所需的技巧MSDN文章这stackoverflow提示,结果在调试器中运行良好。但在调试器之外,它会以"对象引用未设置为对象的实例"失败。
下面是(简化的)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
这样的名字让我很难理解这个问题。