根据我在 C++/CLI 中围绕 CartoType C++ 地图渲染库实现 .NET 包装器的经验,这是另一种方法。这是经过测试和工作的代码。
C++ API 有一个异步 Find 函数,它接受回调:
TResult CartoType::CFramework::FindAsync(FindAsyncCallBack aCallBack,const TFindParam& aFindParam,bool aOverride = false);
回调是这种类型的函数:
using FindAsyncCallBack = std::function<void(std::unique_ptr<CMapObjectArray> aMapObjectArray)>;
任务是通过将 C++/CLI 代码添加到现有的包装器系统来为此函数提供 .NET 包装器。首先,我为我的 .NET 函数定义了一个合适的委托类型(相当于 C++ API 中的 FindAsyncCallback):
public delegate void FindAsyncDelegate(MapObjectList^ aMapObjectList);
.NET 函数的签名如下:
Result FindAsync(FindAsyncDelegate^ aDelegate,FindParam^ aFindParam,bool aOverride);
要解决的主要实现问题是如何调用原生C++函数,并提供一个原生回调函数,然后可以调用.NET函数的调用者传入的委托。一个相关的任务是保持委托和本机回调函数对象处于活动状态,直到异步函数的线程完成其工作。这是它的完成方式。
我定义了一个与 C++ 回调函数类型相同的 C++/CLI 委托类型,以及一个用于保存调用者传递给 .NET 函数(FindAsyncDelegate 类型)的委托的类,以及该类型的委托要传递给 C++(NativeAsyncHandler 类型):
delegate void NativeAsyncHandler(std::unique_ptr<CMapObjectArray> aMapObjectArray);
ref class FindAsyncHelper
{
public:
FindAsyncHelper(Framework^ aFramework,FindAsyncDelegate^ aDelegate):
m_framework(aFramework),
m_delegate(aDelegate)
{
}
void Handler(std::unique_ptr<CMapObjectArray> aMapObjectArray)
{
MapObjectList^ o = gcnew MapObjectList;
SetMapObjectList(m_framework,o,*aMapObjectArray);
m_delegate(o);
// Remove this object from the list held by the framework so that it can be deleted.
m_framework->m_find_async_helper_list->Remove(this);
}
Framework^ m_framework;
FindAsyncDelegate^ m_delegate;
NativeAsyncHandler^ m_native_handler;
};
我们的想法是,我们创建一个包含两个委托的 FindAsyncHelper 对象,然后使用本机委托调用本机 FindAsync 函数,安排调用 Handler(),然后调用原始调用者的委托。
这是它的实现方式:
typedef void(*FIND_ASYNC_CALLBACK)(std::unique_ptr<CMapObjectArray> aMapObjectArray);
Result Framework::FindAsync(FindAsyncDelegate^ aDelegate,FindParam^ aFindParam,bool aOverride)
{
if (aDelegate == nullptr || aFindParam == nullptr)
return Result::ErrorInvalidArgument;
TFindParam param;
SetFindParam(param,aFindParam);
FindAsyncHelper^ h = gcnew FindAsyncHelper(this,aDelegate);
h->m_native_handler = gcnew NativeAsyncHandler(h,&FindAsyncHelper::Handler);
IntPtr p = Marshal::GetFunctionPointerForDelegate(h->m_native_handler);
FIND_ASYNC_CALLBACK f = static_cast<FIND_ASYNC_CALLBACK>(p.ToPointer());
TResult error = m_framework->FindAsync(f,param,aOverride);
// Keep h alive by adding it to a list.
m_find_async_helper_list->Add(h);
return (Result)(int)error;
}
一些注意事项:
声明
FindAsyncHelper^ h = gcnew FindAsyncHelper(this,aDelegate);
h->m_native_handler = gcnew NativeAsyncHandler(h,&FindAsyncHelper::Handler);
创建一个 FindAsyncHandler 对象并在其中存储一个本机处理程序对象;将其保留在这里意味着我们只有一个对象可以保持活动状态,即 FindAsyncHandler。接下来的陈述:
IntPtr p = Marshal::GetFunctionPointerForDelegate(h->m_native_handler);
FIND_ASYNC_CALLBACK f = static_cast<FIND_ASYNC_CALLBACK>(p.ToPointer());
获取一个可以传递给本机代码的函数指针,并将其转换为正确的函数指针类型。我们不能直接将其强制转换为 FindAsyncCallback 中使用的 std::function 类型,所以需要额外的繁琐 typedef。
终于可以调用原生的 FindAsync 函数了:
TResult error = m_framework->FindAsync(f,param,aOverride);
然后,为了确保各种回调函数保持活动状态,将 FindAsyncHandler 添加到主框架对象拥有的列表中:
m_find_async_helper_list->Add(h);
当任务完成并调用 FindAsyncHelper::Handler 时将其从列表中删除。