安全地将回调从托管代码传递到本机代码
safely passing a callback from managed code to native code
我有很多本机 classes 接受某种形式的回调,通常是 boost::signals2::slot
-object。
但为简单起见,我们假设 class:
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);
}
现在我有一个封装本机 class 的托管 class,我想将回调方法传递给本机 class。
public ref class TestWrapper
{
public:
TestWrapper()
: _native(new Test())
{
}
~TestWrapper()
{
delete _native;
}
private:
void CallbackMethod(bool value);
Test* _native;
};
现在通常我会做的是:
- 在我想要的回调的托管包装器中声明一个方法。
- 为此方法创建托管委托对象。
- 使用 GetFunctionPointerForDelegate 获取指向函数的指针
- 将指针指向正确的签名
- 将指针传递给本机 class 作为回调。
- 我还让委托保持活动状态,因为我担心它会被垃圾收集并且我会有一个悬空的函数指针(这个假设正确吗?)
这看起来有点像这样:
_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 中的托管 class 并在 lambda 存在时延长其生命周期。
- 没有强制转换,没有额外的委托类型或成员,最少的额外分配。
问题:
这种方法安全吗?我担心我遗漏了一些东西,这可能会在某些意外情况下导致内存泄漏/未定义的行为。
编辑:
当 lambda 调用其托管包装器 class 的私有方法时,此方法会导致 MethodAccessException
。似乎这种方法必须至少是 internal
.
我认为您不应该使用 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>
};
在我的整个代码库中用这个新方法替换旧方法后,我可以报告它是安全的、更简洁的,而且据我所知,没有发生内存泄漏。
因此,我强烈推荐使用此方法将托管回调传递给本机代码。
我发现的唯一注意事项如下:
- 使用 lambda 表达式强制使用静态方法作为回调注册的助手。这有点hacky。我不清楚为什么 C++-CLI 编译器不允许在标准方法中使用 lambda 表达式。
- lambda 调用的方法必须标记为
internal
,以免在调用时抛出 MethodAccessException
。这是有道理的,因为它不是在 class 范围内调用的。但是,使用 C# 的委托/lambda 仍然没有这个限制。
我有很多本机 classes 接受某种形式的回调,通常是 boost::signals2::slot
-object。
但为简单起见,我们假设 class:
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);
}
现在我有一个封装本机 class 的托管 class,我想将回调方法传递给本机 class。
public ref class TestWrapper
{
public:
TestWrapper()
: _native(new Test())
{
}
~TestWrapper()
{
delete _native;
}
private:
void CallbackMethod(bool value);
Test* _native;
};
现在通常我会做的是:
- 在我想要的回调的托管包装器中声明一个方法。
- 为此方法创建托管委托对象。
- 使用 GetFunctionPointerForDelegate 获取指向函数的指针
- 将指针指向正确的签名
- 将指针传递给本机 class 作为回调。
- 我还让委托保持活动状态,因为我担心它会被垃圾收集并且我会有一个悬空的函数指针(这个假设正确吗?)
这看起来有点像这样:
_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 中的托管 class 并在 lambda 存在时延长其生命周期。
- 没有强制转换,没有额外的委托类型或成员,最少的额外分配。
问题:
这种方法安全吗?我担心我遗漏了一些东西,这可能会在某些意外情况下导致内存泄漏/未定义的行为。
编辑:
当 lambda 调用其托管包装器 class 的私有方法时,此方法会导致 MethodAccessException
。似乎这种方法必须至少是 internal
.
我认为您不应该使用 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>
};
在我的整个代码库中用这个新方法替换旧方法后,我可以报告它是安全的、更简洁的,而且据我所知,没有发生内存泄漏。
因此,我强烈推荐使用此方法将托管回调传递给本机代码。
我发现的唯一注意事项如下:
- 使用 lambda 表达式强制使用静态方法作为回调注册的助手。这有点hacky。我不清楚为什么 C++-CLI 编译器不允许在标准方法中使用 lambda 表达式。
- lambda 调用的方法必须标记为
internal
,以免在调用时抛出MethodAccessException
。这是有道理的,因为它不是在 class 范围内调用的。但是,使用 C# 的委托/lambda 仍然没有这个限制。