2016-08-18 90 views
4

我正在尝试将PlayFab C++ SDK包含到我的应用中。该SDK主要用于游戏引擎Cocos2d-x,但基本上可用于任何C++应用程序。使用std :: function替换函数指针实现以在PlayFab SDK中使用lambdas

这只是普通的REST,因此您可以向服务器发送请求并等待响应。这对于使用lambda来说是完美的。

他们宣布这个回调,被称为当一个请求成功:

template<typename ResType> using ProcessApiCallback = void(*)(const ResType& result, void* userData); 

不幸的是,他们没有使用std ::函数,而是一个函数指针。这样就不能使用捕获变量的lambda表达式。

因此,我想用的std ::回调函数,像这样我可以简单地替换这个函数指针回调:

template<typename ResType> using ProcessApiCallback = std::function<void(const ResType& result, void* userData)>; 

不幸的是,事情没有那么简单,因为它们存储在使用丑陋reinterpret_casts函数指针这里有一个例子(去除不必要的部分,以保持它的简称):

void PlayFabClientAPI::LoginWithAndroidDeviceID(
    LoginWithAndroidDeviceIDRequest& request, 
    ProcessApiCallback<LoginResult> callback, 
    ErrorCallback errorCallback, 
    void* userData 
    ) 
{ 
    // here they assign the callback to the httpRequest 
    httpRequest->SetResultCallback(reinterpret_cast<void*>(callback)); 
    httpRequest->SetErrorCallback(errorCallback); 
    httpRequest->SetUserData(userData); 

    PlayFabSettings::httpRequester->AddRequest(httpRequest, OnLoginWithAndroidDeviceIDResult, userData); 
} 

后来,当请求是成功的,他们这样做:

if (request->GetResultCallback() != nullptr) 
{ 
    ProcessApiCallback<LoginResult> successCallback = reinterpret_cast<ProcessApiCallback<LoginResult>>(request->GetResultCallback()); 
    successCallback(outResult, request->GetUserData()); 
} 

的HttpRequest类具有这一领域:

void* mResultCallback; 

的问题是,我不知道如何随心所欲的std ::函数指针存储在HttpRequest类再后来投他们回来。我尝试了很多东西,包括也很丑陋的reinterpret_casting,但没有任何工作。

我可以对其SDK进行任何更改。我也将这个报告称为糟糕的设计,他们同意,但他们没有时间去改进它,但是如果能找到一个好的解决方案,他们会接受pull request。

+0

你有C++ 17'的std :: any'或'提振:: any'?这些让它变得简单。 – Yakk

+0

不幸的是,我只限于C++ 11,并且如果可能的话不会增加。 – keyboard

回答

1

这里的关键信息是userData指针。它作为请求的一部分提供,并传递回您的回调函数。这是一个不透明的指针,图书馆不会注意,否则,除了将其转发给您的回调。

这就是你将要使用的,在这里。

这是一个非常常见的设计模式,它使用C语言编写的面向服务的通用库。它们的API通常以这种方式构造:它们接受带有额外不透明指针的请求。它们存储这个指针,并在请求完成时将它传递回用户提供的回调函数。

然后,该回调使用它将任何类型的附加元数据与请求相关联。

这是一个C++库,但他们选择为库回调实现C风格的设计模式。那真不幸。

但是,无论如何,您将动态分配您的std::function或包含您的std::function的某个类的实例以及它需要的任何其他数据,并将指针传递给动态分配的结构请求。

当你的回调被调用时,它只需要reinterpret_cast指向动态分配类型的不透明指针,复制它的内容,delete它(为了避免内存泄漏,当然),然后继续使用复制的内容作为回调动作的一部分(无论是涉及调用std::function还是其他内容都不重要)。

鉴于这是一个您正在使用的C++库,而不是一个C库,不幸的是,他们选择实现这种C风格的不透明指针传递设计模式。当然,有更好的方法来在C++中实现它,但这是你必须处理的,所以你必须处理一个丑陋的reintepret_cast。没办法避免它。

1

为了详细说明通过@SamVarshavchik答案有点这里有一个代码段应处理大多数产生userData指针和“无状态”回调函数取userData你的过程:

#include <memory> 
#include <utility> 
#include <type_traits> 
#include <iostream> 

template <typename T, typename... Args> 
void c_callback_adaptor(Args... args, void* userData) { 
    auto callable = reinterpret_cast<T*>(userData); 
    (*callable)(args...); 
} 

template <typename Fn> 
struct genCCallback_impl; 
template <typename Res, typename... Args> 
struct genCCallback_impl<Res(Args...)> { 
    template <typename T> 
    static std::pair<std::unique_ptr<std::decay_t<T>>, 
        Res (*)(Args..., void*)> 
    gen(T&& callable) { 
     return std::make_pair(
      std::make_unique<std::decay_t<T>>(std::forward<T>(callable)), 
      c_callback_adaptor<std::decay_t<T>, Args...>); 
    } 
}; 

template <typename Fn, typename T> 
auto genCCallback(T&& callable) { 
    return genCCallback_impl<Fn>::gen(std::forward<T>(callable)); 
} 

和使用一个简单的例子:

extern void registerCallback(void (*callbackFn)(int, int, void*), void* userData); 

void test(int n) { 
    auto cCallback = genCCallback<void(int, int)>(
     [n](int x, int y) { 
      std::cout << n << ' ' << x << ' ' << y << '\n'; 
     }); 
    registerCallback(cCallback.second, cCallback.first.get()); 
    // for demo purposes make the allocated memory permanent 
    (void) cCallback.first.release(); 
} 

(当然,在实际的代码,你需要跟踪std::unique_ptr的,直到你准备注销回调。)

在回答评论:要分解模板在幕后做的事情,假设lambda类的内部名称为__lambda1。那么,上述test()函数生成的代码基本上等同于:

void c_callback_adaptor_lambda1(int x, int y, void* userData) { 
    auto callable = reinterpret_cast<__lambda1*>(userData); 
    (*callable)(x, y); 
} 

class __lambda1 { 
public: 
    __lambda1(int n) { ... } 
    void operator()(int x, int y) const { std::cout << ...; } 
    ... 
private: ... 
}; 

void test(int n) { 
    auto cCallback = std::make_pair(
     std::make_unique<__lambda1>(n), 
     c_callback_adaptor_lambda1); 
    registerCallback(cCallback.second, cCallback.first.get()); 
    (void) cCallback.first.release(); 
} 
+0

哇,这是很多魔术,超过我的头。我希望我可以在盯着它时足够长的时间来解释这里究竟发生了什么) – keyboard

+0

好的,我已经在例子test()中添加了一个模板代码生成的例子。功能。 –

0

已链接已经被升级到支持lambda函数不修改SDK的PlayFab Cocos2dxSDK。

例如:

PlayFabClientAPI::LoginWithEmailAddress(request, 
    [](const LoginResult& result, void* customData) { /* your login-success lambda here */ }, 
    [](const PlayFabError& error, void* customData) { /* your error lambda here */ }, 
    nullptr); 
+0

是的,因为我将它们编码并给他们发送代码以包含它;) – keyboard

相关问题