I'm currently trying to include the PlayFab C++ SDK into my app. This SDK is mainly for the game engine Cocos2d-x, but can be used for any C++ app basically.
It's just plain REST, hence you send requests to their servers and wait for the response. This would be perfect for using lambdas.
They declare this callback, which is called when a request is successful:
template<typename ResType> using ProcessApiCallback = void(*)(const ResType& result, void* userData);
Unfortunately, they're not using std::function, but a function pointer. This way one can not use lambdas that capture variables.
Hence, I thought I could simply replace this function pointer callback with an std::function callback like so:
template<typename ResType> using ProcessApiCallback = std::function<void(const ResType& result, void* userData)>;
Unfortunately, things are not that simply, as they stored the function pointers using ugly reinterpret_casts, here's an example (remove unnecessary parts to keep it short):
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);
}
Later on, when the request was successful, they do this:
if (request->GetResultCallback() != nullptr)
{
ProcessApiCallback<LoginResult> successCallback = reinterpret_cast<ProcessApiCallback<LoginResult>>(request->GetResultCallback());
successCallback(outResult, request->GetUserData());
}
The HttpRequest class has this field:
void* mResultCallback;
The problem is that I don't know how to store arbitrary std::function pointers in the HttpRequest class and then later cast them back. I tried many things, including also really ugly reinterpret_casting, but nothing did work.
I'm open to do any changes to their SDK. I also reported this as bad design and they agreed, but they don't have the time to improve it, but they will accept pull request if a good solution can be found.
The key item of information here is the userData
pointer. It is supplied as part of the request, and it gets passed back to your callback function. This is an opaque pointer that the library pays no attention to, otherwise, except to forward it to your callback.
And this is what you will use, here.
This is a very common design pattern with generic service-oriented libraries that are written in C. Their APIs are often structured this way: they accept a request with an extra opaque pointer. They store this pointer, and they pass it back to the user-supplied callback, when the request completes.
The callback, then, uses it to associated any kind of additional metadata with the request.
This is a C++ library, but they chose to implement a C-style design pattern for library callbacks. That's unfortunate.
But, anyway, in your case you're going to dynamically allocate either your std::function
, or some class's instance that contains your std::function
, and any other data it needs, and pass the pointer to the dynamically-allocated structure to the request.
When your callback gets invoked, it simply needs to reinterpret_cast
the opaque pointer to the dynamically-allocated type, copy its contents, delete
it (in order to avoid memory leaks, of course), then proceed to use the copied contents as part of the callback action (whether it involves invoking the std::function
, or something else, is immaterial).
Given that this is a C++ library you're using, and not a C library, it is unfortunate that they chose to implement this C-style opaque pointer pass-through design pattern. There are, of course, better ways to implement this in C++, but this is what you have to work with, so you'll have to deal with one ugly reintepret_cast
. No way to avoid it.