我们都习惯了在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回收。而我们传入非托管对象的是其地址,那么一旦委托被回收,则托管部分运行就会出错。

参考链接:


本文会经常更新,请阅读原文: https://xinyuehtx.github.io/post/C++CLI%E5%A7%94%E6%89%98%E5%9B%9E%E8%B0%83.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。

知识共享许可协议 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名黄腾霄(包含链接: https://xinyuehtx.github.io ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系