安全地将回调从托管代码传递到本机代码

Nin*_*rez 5 c++-cli c++11

我有很多本机类都接受某种形式的回调,通常是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);
}
Run Code Online (Sandbox Code Playgroud)

现在,我有一个包装了该本机类的托管类,并且我想将回调方法传递给本机类。

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

    }

    ~TestWrapper()
    {
        delete _native;
    }
private:
    void CallbackMethod(bool value);
    Test* _native;
};
Run Code Online (Sandbox Code Playgroud)

现在通常我会执行以下操作:

  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);
Run Code Online (Sandbox Code Playgroud)

我想减少代码量,而不必执行任何强制类型转换或声明任何委托类型。我想使用没有委托的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;
    }
    );
}
Run Code Online (Sandbox Code Playgroud)
  • 声明一个this可以使用C ++ 11 lambda 接受的静态方法。
  • 使用gcroot可以捕获lambda中的托管类,并在lambda有效时延长其寿命。
  • 没有强制转换,没有额外的委托人类型或成员,最少的额外分配。

问题:
这种方法安全吗?我担心我会丢失某些东西,并且在某些无法预料的情况下,这会导致内存泄漏/不确定的行为。

编辑:

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

Ela*_*oni 1

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

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

我发现的唯一警告如下:

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