Lv.的博客

C++/CLI委托回调



我们都习惯了在c#中使用事件,但是c++中没有默认的事件机制,所以在编写c++/cli时,这将是一个令人困扰的问题

在c++中常见的方式是传入一个回调,在特定的时机,通过调用回调函数,执行上层的代码。
这种方式能够解决一部分场景的问题,但是另外一些场景,比如事件的invok列表,责任链模式都不能实现。
另外常见的回调对象,要求一次实现多个回调函数,这样容易将各个业务的处理都聚集在同一个回调对象中,不利于解耦。
在c++/cli中,一种可以参考的处理方式是使用一个托管类对非托管的回调类进行封装。向非托管的回调类传入一个托管类的委托函数后,由委托函数重新引发.NET事件。
//非托管部分Callback.h
public class Callback:Public AbstractCallback
{
private:
typedef std::function<void(void)> FooCallback;
FooCallback _fooCallback;
public:
inline void OnFoo() override
{
if (_fooCallback)
{
_fooCallback();
}
}

inline void SetFooCallback(FooCallback callback)
{
_fooCallback=callback;
}

}

//托管部分Wrapper.h
public ref class Wrapper
{
private:
Callback* _callback;
delegate void DelegateOnFoo();
DelegateOnFoo^ _onFoo;
void InitCallback();
void OnFoo(){FooEvent(this,gcnew EventArgs())}
public:
event EventHandler^ FooEvent;
}

//托管部分Wrapper.cpp
void Wrapper::InitCallback()
{
using namespace std;
_callback = new Callback();
_onFoo = gcnew DelegateOnFoo(this, &Wrapper::OnFoo);
IntPtr pvFun1 = Marshal::GetFunctionPointerForDelegate(safe_cast<Delegate^>(_onFoo));
_callback->SetFooCallback((void(*)(void))pvFun1.ToPointer());
}

其中需要注意的几点是:
我们生成了一个托管的委托,DelegateOnFoo,但是对于非托管的回调来说他只能接受对应的函数指针,因此,需要使用Marshal::GetFunctionPointerForDelegate将其转换为指针
此时我们得到的是一个IntPtr智能指针对象,我们需要使用ToPointer方法将其转换为void*,然后再强转为参数匹配的(void(*)(void))格式,才能传入非托管方法
另外,最容易忽略的一点是我们通过_onFoo字段保留了这个委托的引用。对于DelegateOnFoo来说,他是一个.NET对象,由gcnew生成,通过gc进行内存管理,如果不保留引用则随时可能被gc回收。而我们传入非托管对象的是其地址,那么一旦委托被回收,则托管部分运行就会出错。

posted @ 2020-11-12 11:17  Avatarx  阅读(720)  评论(0编辑  收藏  举报