我有一个本机的,非托管的C库,我希望将其包装在托管C类中,以提供干净且类型安全的方式来从C#访问非托管类,而无需执行PInvoke.
我试图包装的方法有以下签名:
void Unmanaged::login(
const std::wstring& email,
const std::wstring& password,
std::function<void()> on_success,
std::function<void(int, const std::wstring&)> on_error);
然而,试图包装它并不容易.显而易见的方式:
public delegate void LoginSuccess();
public delegate void LoginFailed(int, String^);
void Wrapper::login(String ^ email, String ^ password, LoginSuccess ^ onSuccess, LoginFailed ^ one rror)
{
unmanaged->login(convert_to_unmanaged_string(email), convert_to_unmanaged_string(password), [onSuccess]() {onSuccess(); }, [](int code, const std::wstring& msg) {onError(code,convert_to_managed_string(msg))});
}
失败,因为托管C不允许(本地)lambdas(在成员中).
我知道我可以使用Marshal :: GetFunctionPointerForDelegate来获取指向委托的本机指针,但我仍然需要提供一个“中间件”来在托管/非托管类型(例如std :: wstring)之间进行转换.
是否有比使用托管C更好的方法?
解决方法:
您的代码无法编译,因为您无法捕获本机lambda中的托管对象.但是,在gcroot类的帮助下,您可以轻松地将托管对象包装在非托管对象中:
你需要这些标题:
#include <vcclr.h>
#include <msclr/marshal_cppstd.h>
这是包装代码:
static void managedLogin(String^ email, String^ password, LoginSuccess^ onSuccess, LoginFailed^ one rror)
{
gcroot<LoginSuccess^> onSuccessWrapper(onSuccess);
gcroot<LoginFailed^> one rrorWrapper(onError);
Unmanaged::login(
msclr::interop::marshal_as<std::wstring>(email),
msclr::interop::marshal_as<std::wstring>(password),
[onSuccessWrapper]() {
onSuccessWrapper->Invoke();
},
[onErrorWrapper](int code, const std::wstring& message) {
one rrorWrapper->Invoke(code, msclr::interop::marshal_as<String^>(message));
}
);
}
public ref class Wrapper
{
public:
static void login(String ^ email, String ^ password, LoginSuccess ^ onSuccess, LoginFailed ^ one rror)
{
managedLogin(email, password, onSuccess, one rror);
}
};
gcroot
对象包装了一个System::Runtime::InteropServices::GCHandle
,这将使托管对象保持活动状态.这是一个可以在lambda中捕获的非托管类.一旦你知道这一点,其余的都很简单.
出于某种原因,如果你尝试在成员函数中使用lambda,编译器会抱怨,但是在*函数中使用lambda完全没问题.去搞清楚.