2016-08-18 89 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

相關問題