将函数指针实现替换为 std::function,以便在 PlayFab SDK 中使用 lambda
Replace function-pointer implementation with std::function for using lambdas in PlayFab SDK
我目前正在尝试将 PlayFab C++ SDK 添加到我的应用程序中。此SDK主要针对游戏引擎Cocos2d-x,但基本上可以用于任何C++应用。
它只是普通的 REST,因此您向他们的服务器发送请求并等待响应。这将非常适合使用 lambda。
他们声明了这个回调,在请求成功时调用:
template<typename ResType> using ProcessApiCallback = void(*)(const ResType& result, void* userData);
不幸的是,他们没有使用 std::function,而是一个函数指针。这样就不能使用捕获变量的 lambda。
因此,我认为我可以简单地将此函数指针回调替换为 std::function 回调,如下所示:
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 class 有这个字段:
void* mResultCallback;
问题是我不知道如何在 HttpRequest class 中存储任意 std::function 指针,然后再将它们转换回去。我尝试了很多东西,包括也很丑 reinterpret_casting,但没有任何效果。
我愿意对他们的 SDK 进行任何更改。我也报告这是糟糕的设计,他们同意了,但他们没有时间改进它,但如果能找到好的解决方案,他们会接受拉取请求。
这里的关键信息是userData
指针。它作为请求的一部分提供,并传递回您的回调函数。这是一个库不关注的不透明指针,否则,除了将其转发给您的回调。
这就是您将在此处使用的内容。
这是一种非常常见的设计模式,具有用 C 编写的通用面向服务的库。它们的 API 通常以这种方式构建:它们接受带有额外不透明指针的请求。他们存储这个指针,并在请求完成时将其传递回用户提供的回调。
回调然后使用它将任何类型的附加元数据与请求相关联。
这是一个 C++ 库,但他们选择为库回调实现 C 风格的设计模式。真不幸。
但是,无论如何,在您的情况下,您将动态分配您的 std::function
或包含您的 std::function
和任何其他数据的某些 class 的实例它需要,并将指向动态分配结构的指针传递给请求。
当你的回调被调用时,它只需要 reinterpret_cast
指向动态分配类型的不透明指针,复制它的内容,delete
它(当然是为了避免内存泄漏),然后继续使用复制的内容作为回调操作的一部分(是否涉及调用 std::function
或其他内容并不重要)。
鉴于这是您正在使用的 C++ 库,而不是 C 库,很遗憾他们选择实现这种 C 风格的不透明指针传递设计模式。当然,在 C++ 中有更好的方法来实现它,但这是你必须处理的,所以你将不得不处理一个丑陋的 reintepret_cast
。没办法避免。
为了详细说明@SamVarshavchik 的回答,这里有一个代码片段,它应该处理生成 userData
指针和采用 [=13= 的 "stateless" 回调函数的大部分过程] 给你:
#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 class 的内部名称是 __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();
}
您链接的 PlayFab Cocos2dxSDK 已升级为支持 lambda 函数,无需修改 SDK。
例如:
PlayFabClientAPI::LoginWithEmailAddress(request,
[](const LoginResult& result, void* customData) { /* your login-success lambda here */ },
[](const PlayFabError& error, void* customData) { /* your error lambda here */ },
nullptr);
我目前正在尝试将 PlayFab C++ SDK 添加到我的应用程序中。此SDK主要针对游戏引擎Cocos2d-x,但基本上可以用于任何C++应用。
它只是普通的 REST,因此您向他们的服务器发送请求并等待响应。这将非常适合使用 lambda。
他们声明了这个回调,在请求成功时调用:
template<typename ResType> using ProcessApiCallback = void(*)(const ResType& result, void* userData);
不幸的是,他们没有使用 std::function,而是一个函数指针。这样就不能使用捕获变量的 lambda。
因此,我认为我可以简单地将此函数指针回调替换为 std::function 回调,如下所示:
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 class 有这个字段:
void* mResultCallback;
问题是我不知道如何在 HttpRequest class 中存储任意 std::function 指针,然后再将它们转换回去。我尝试了很多东西,包括也很丑 reinterpret_casting,但没有任何效果。
我愿意对他们的 SDK 进行任何更改。我也报告这是糟糕的设计,他们同意了,但他们没有时间改进它,但如果能找到好的解决方案,他们会接受拉取请求。
这里的关键信息是userData
指针。它作为请求的一部分提供,并传递回您的回调函数。这是一个库不关注的不透明指针,否则,除了将其转发给您的回调。
这就是您将在此处使用的内容。
这是一种非常常见的设计模式,具有用 C 编写的通用面向服务的库。它们的 API 通常以这种方式构建:它们接受带有额外不透明指针的请求。他们存储这个指针,并在请求完成时将其传递回用户提供的回调。
回调然后使用它将任何类型的附加元数据与请求相关联。
这是一个 C++ 库,但他们选择为库回调实现 C 风格的设计模式。真不幸。
但是,无论如何,在您的情况下,您将动态分配您的 std::function
或包含您的 std::function
和任何其他数据的某些 class 的实例它需要,并将指向动态分配结构的指针传递给请求。
当你的回调被调用时,它只需要 reinterpret_cast
指向动态分配类型的不透明指针,复制它的内容,delete
它(当然是为了避免内存泄漏),然后继续使用复制的内容作为回调操作的一部分(是否涉及调用 std::function
或其他内容并不重要)。
鉴于这是您正在使用的 C++ 库,而不是 C 库,很遗憾他们选择实现这种 C 风格的不透明指针传递设计模式。当然,在 C++ 中有更好的方法来实现它,但这是你必须处理的,所以你将不得不处理一个丑陋的 reintepret_cast
。没办法避免。
为了详细说明@SamVarshavchik 的回答,这里有一个代码片段,它应该处理生成 userData
指针和采用 [=13= 的 "stateless" 回调函数的大部分过程] 给你:
#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 class 的内部名称是 __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();
}
您链接的 PlayFab Cocos2dxSDK 已升级为支持 lambda 函数,无需修改 SDK。
例如:
PlayFabClientAPI::LoginWithEmailAddress(request,
[](const LoginResult& result, void* customData) { /* your login-success lambda here */ },
[](const PlayFabError& error, void* customData) { /* your error lambda here */ },
nullptr);