从非托管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可以减少托管堆的碎片可能性。"

怎么了?

从非托管c++到c#的回调工作,但仅在调试器中

您必须分配委托本身,而不是它的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();
    }

}

顺便说一下,我希望你有更强大的命名系统。像DoCSharpTheCallBacktheCallBack这样的名字让我很难理解这个问题。