在std :: function中包装委托?

时间:2016-07-01 14:06:33

标签: c# c++ .net c++-cli clr

我有一个原生的,非托管的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 ^ 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 ++不允许(本地)lambdas(在成员中)。

我知道我可以使用Marshal :: GetFunctionPointerForDelegate来获取指向委托的本机指针,但我仍然需要提供一个&#34;中间件&#34;在托管/非托管类型(例如std :: wstring)之间进行转换。

是否有比使用托管C ++更好的方法?

2 个答案:

答案 0 :(得分:4)

您的代码无法编译,因为您无法捕获本机lambda中的托管对象。但是,在gcroot类的帮助下,您可以轻松地将托管对象包装在非托管对象中:

您需要这些标题:

#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对象包装System::Runtime::InteropServices::GCHandle,这将使托管对象保持活动状态。它是一个可以在lambda中捕获的非托管类。一旦你知道这一点,剩下的就是直截了当。

出于某种原因,如果你试图在成员函数中使用lambda,编译器会抱怨,但是在自由函数中使用lambda它完全没问题。去图。

答案 1 :(得分:1)

这是我能够快速提出的最好的事情。我认为你将会被包装手插入手中,将你的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);
}