【问题标题】:Callback from unmanaged C++ to C# works, but only in debugger从非托管 C++ 到 C# 的回调有效,但仅在调试器中有效
【发布时间】: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 想发布答案,我会接受。

标签: c# c++-cli


【解决方案1】:

您必须分配委托本身,而不是其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 等名字让我很难理解这个问题。

【讨论】:

  • 感谢您发现我的错误,挂在委托上确实有效。抱歉,命名令人困惑。
  • 感谢这个简单的例子。它解决了我的几个问题并且非常有帮助!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-09-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-17
  • 1970-01-01
相关资源
最近更新 更多