从非托管C ++到C#的回调可以正常工作,但仅限于调试器

时间:2011-12-13 15:09:42

标签: c# c++-cli

从非托管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减少了托管堆的碎片潜力。“

怎么了?

1 个答案:

答案 0 :(得分:7)

您必须分配代理本身,而不是IntPtr。此外,当您完成GCHandle实例时,您必须释放CSharpCode

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等名称让我很难理解这个问题。