在 std::function 中包装委托?
Wrap delegate in std::function?
我有一个本机非托管 C++ 库,我想将其包装在托管 C++ class 中以提供干净且类型安全的方式来从 C# 访问非托管 class 而无需执行 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 ^ onError)
{
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++ 不允许(本地)lambda(在成员中)。
我知道我可以使用 Marshal::GetFunctionPointerForDelegate 来获取指向委托的本机指针,但我仍然需要提供一个 "middleware" 来在 managed/unmanaged 类型之间进行转换(例如 std::wstring).
是否有比完全使用托管 C++ 更好的方法?
这是我能很快想出的最好的办法。我认为你会被包装器困住,将你的 args 手动编组回托管类型,除非有一些我不知道的整个自动回调编组事情。这是很有可能的,因为我只做了一个星期的托管编程。
delegate void CBDelegate(String^ v);
typedef void(*CBType)(String^);
typedef std::function<void(const wchar_t*)> cb_type;
cb_type MakeCB(CBType f)
{
gcroot<CBDelegate^> f_wrap(gcnew CBDelegate(f));
auto cb = [f_wrap](const wchar_t *s) {
f_wrap->Invoke(gcnew String(s));
};
return cb;
}
void f(cb_type);
void MyCallback(String^ s) {
Console::WriteLine("MyCallback {0}", s);
}
void main() {
f(MakeCB(MyCallback));
}
#pragma unmanaged
void f(cb_type cb)
{
cb(L"Hello");
}
编辑 1:改进代码。
编辑 2:窃取@Lucas Trzesniewski 的好点子。也不需要 CBWrap
编辑 3:如果您更喜欢包装函数而不是回调。
delegate void CBDelegate(String^ v);
typedef void(*CBType)(String^);
typedef std::function<void(const wchar_t*)> cb_type;
void f(cb_type);
void f_wrap(CBType cb) {
gcroot<CBDelegate^> cb_wrap(gcnew CBDelegate(cb));
auto cb_lambda = [cb_wrap](const wchar_t *s) {
cb_wrap->Invoke(gcnew String(s));
};
f(cb_lambda);
}
void MyCallback(String^ s) {
Console::WriteLine("MyCallback {0}", s);
}
void main() {
f_wrap(MyCallback);
}
您的代码无法编译,因为您无法在本机 lambda 中捕获托管 object。但是在 gcroot
class:
的帮助下,您可以轻松地将托管 object 包装在非托管中
你需要这些 headers:
#include <vcclr.h>
#include <msclr/marshal_cppstd.h>
这是包装代码:
static void managedLogin(String^ email, String^ password, LoginSuccess^ onSuccess, LoginFailed^ onError)
{
gcroot<LoginSuccess^> onSuccessWrapper(onSuccess);
gcroot<LoginFailed^> onErrorWrapper(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) {
onErrorWrapper->Invoke(code, msclr::interop::marshal_as<String^>(message));
}
);
}
public ref class Wrapper
{
public:
static void login(String ^ email, String ^ password, LoginSuccess ^ onSuccess, LoginFailed ^ onError)
{
managedLogin(email, password, onSuccess, onError);
}
};
gcroot
object wraps a System::Runtime::InteropServices::GCHandle
,这将使托管 object 保持活动状态。这是一个非托管的 class,您可以在 lambda 中捕获。一旦你知道了这一点,剩下的就很简单了。
出于某种原因,如果您尝试在 member 函数中使用 lambda,编译器会报错,但在自由函数中使用 lambda 完全没问题。去图吧。
我有一个本机非托管 C++ 库,我想将其包装在托管 C++ class 中以提供干净且类型安全的方式来从 C# 访问非托管 class 而无需执行 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 ^ onError)
{
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++ 不允许(本地)lambda(在成员中)。
我知道我可以使用 Marshal::GetFunctionPointerForDelegate 来获取指向委托的本机指针,但我仍然需要提供一个 "middleware" 来在 managed/unmanaged 类型之间进行转换(例如 std::wstring).
是否有比完全使用托管 C++ 更好的方法?
这是我能很快想出的最好的办法。我认为你会被包装器困住,将你的 args 手动编组回托管类型,除非有一些我不知道的整个自动回调编组事情。这是很有可能的,因为我只做了一个星期的托管编程。
delegate void CBDelegate(String^ v);
typedef void(*CBType)(String^);
typedef std::function<void(const wchar_t*)> cb_type;
cb_type MakeCB(CBType f)
{
gcroot<CBDelegate^> f_wrap(gcnew CBDelegate(f));
auto cb = [f_wrap](const wchar_t *s) {
f_wrap->Invoke(gcnew String(s));
};
return cb;
}
void f(cb_type);
void MyCallback(String^ s) {
Console::WriteLine("MyCallback {0}", s);
}
void main() {
f(MakeCB(MyCallback));
}
#pragma unmanaged
void f(cb_type cb)
{
cb(L"Hello");
}
编辑 1:改进代码。
编辑 2:窃取@Lucas Trzesniewski 的好点子。也不需要 CBWrap
编辑 3:如果您更喜欢包装函数而不是回调。
delegate void CBDelegate(String^ v);
typedef void(*CBType)(String^);
typedef std::function<void(const wchar_t*)> cb_type;
void f(cb_type);
void f_wrap(CBType cb) {
gcroot<CBDelegate^> cb_wrap(gcnew CBDelegate(cb));
auto cb_lambda = [cb_wrap](const wchar_t *s) {
cb_wrap->Invoke(gcnew String(s));
};
f(cb_lambda);
}
void MyCallback(String^ s) {
Console::WriteLine("MyCallback {0}", s);
}
void main() {
f_wrap(MyCallback);
}
您的代码无法编译,因为您无法在本机 lambda 中捕获托管 object。但是在 gcroot
class:
你需要这些 headers:
#include <vcclr.h>
#include <msclr/marshal_cppstd.h>
这是包装代码:
static void managedLogin(String^ email, String^ password, LoginSuccess^ onSuccess, LoginFailed^ onError)
{
gcroot<LoginSuccess^> onSuccessWrapper(onSuccess);
gcroot<LoginFailed^> onErrorWrapper(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) {
onErrorWrapper->Invoke(code, msclr::interop::marshal_as<String^>(message));
}
);
}
public ref class Wrapper
{
public:
static void login(String ^ email, String ^ password, LoginSuccess ^ onSuccess, LoginFailed ^ onError)
{
managedLogin(email, password, onSuccess, onError);
}
};
gcroot
object wraps a System::Runtime::InteropServices::GCHandle
,这将使托管 object 保持活动状态。这是一个非托管的 class,您可以在 lambda 中捕获。一旦你知道了这一点,剩下的就很简单了。
出于某种原因,如果您尝试在 member 函数中使用 lambda,编译器会报错,但在自由函数中使用 lambda 完全没问题。去图吧。