【问题标题】:safely passing a callback from managed code to native code安全地将回调从托管代码传递到本机代码
【发布时间】:2019-07-24 07:21:32
【问题描述】:

我有很多本地类接受某种形式的回调,通常是boost::signals2::slot-object。

但为了简单起见,我们假设类:

class Test
{
    // set a callback that will be invoked at an unspecified time
    // will be removed when Test class dies
    void SetCallback(std::function<void(bool)> callback);
}

现在我有一个封装了这个原生类的托管类,我想给原生类传递一个回调方法。

public ref class TestWrapper
{
public:
    TestWrapper()
        : _native(new Test())
    {

    }

    ~TestWrapper()
    {
        delete _native;
    }
private:
    void CallbackMethod(bool value);
    Test* _native;
};

现在我通常会做以下事情:

  1. 在托管包装器中声明一个方法,这是我想要的回调。
  2. 为此方法创建一个托管委托对象。
  3. 使用 GetFunctionPointerForDelegate 获取指向函数的指针
  4. 将指针指向正确的签名
  5. 将指针作为回调传递给本机类。
  6. 我还让委托保持活动状态,因为我担心它会被垃圾回收并且我会有一个悬空函数指针(这个假设是否正确?)

这看起来有点像这样:

_managedDelegateMember = gcnew ManagedEventHandler(this, &TestWrapper::Callback);
System::IntPtr stubPointer = Marshal::GetFunctionPointerForDelegate(_managedDelegateMember);
UnmanagedEventHandlerFunctionPointer  functionPointer = static_cast<UnmanagedEventHandlerFunctionPointer >(stubPointer.ToPointer());   
_native->SetCallback(functionPointer);

我想减少代码量并且不必执行任何强制转换或声明任何委托类型。我想使用没有委托的 lambda 表达式。

这是我的新方法:

static void SetCallbackInternal(TestWrapper^ self)
{
    gcroot<TestWrapper^> instance(self);
    self->_native->SetCallback([instance](bool value)
    {
        // access managed class from within native code
        instance->Value = value;
    }
    );
}
  • 声明一个接受 this 的静态方法,以便能够使用 C++11 lambda。
  • 使用 gcroot 捕获 lambda 中的托管类,并在 lambda 处于活动状态时延长其生命周期。
  • 没有强制转换,没有额外的委托类型和成员,最少的额外分配。

问题:
这种方法安全吗?我担心我遗漏了一些东西,这可能会在某些意外情况下导致内存泄漏/未定义的行为。

编辑:

当 lambda 调用其托管包装类的私有方法时,这种方法会导致 MethodAccessException。看来这个方法至少必须是internal

【问题讨论】:

    标签: c++11 c++-cli


    【解决方案1】:

    我认为您不应该使用 gcroot 而是使用共享指针。只要有人在使用共享指针,就可以使对象保持活动状态。

    您还应该在整个代码中使用更多的 c++ 风格,将原始指针替换为智能指针和模板而不是 std::function(lambda 可以存储在编译时类型中)。

    例如使用您发布的代码:

    class Test
    {
        // set a callback that will be invoked at an unspecified time
        // will be removed when Test class dies
        template <class T>
        void SetCallback(T callback); // Replaced std::function<void(bool)> with T
    }
    
    public ref class TestWrapper
    {
    public:
        TestWrapper()
            : _native()
        {}
    private:
        void CallbackMethod(bool value);
        std::unique_ptr<Test> _native;  // Replaced Test* with std::unique_ptr<Test>
    };
    

    【讨论】:

    • 您的解决方案不正确。这是 C++-CLI,您不能将成员放入不是原始本机类型或 ref 类本身的 ref 类中。
    【解决方案2】:

    在我的代码库中用这种新方法替换旧方法后,我可以报告它是安全的,更简洁,据我所知,没有发生内存泄漏。

    因此,我强烈推荐使用这种方法将托管回调传递给本机代码。

    我发现的唯一警告如下:

    • 使用 lambda 表达式会强制使用静态方法作为回调注册的帮助程序。这有点hacky。我不清楚为什么 C++-CLI 编译器不允许在标准方法中使用 lambda 表达式。
    • lambda 调用的方法必须标记为internal,以便在调用时不会抛出MethodAccessException。这是有道理的,因为它不是在类范围本身内调用的。但是,使用 C# 的委托/lambda 仍然没有这种限制。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-11-01
      • 2011-09-06
      • 2023-04-01
      • 1970-01-01
      • 2011-01-21
      • 2013-03-06
      • 1970-01-01
      相关资源
      最近更新 更多