将任意数据传递给不接受“ void * userarg”的C ++回调

时间:2018-07-02 13:54:31

标签: c++ scope callback singleton global-variables

编辑:

我修改了示例API,以便更好地反映我正在处理的真实API。 API传递message参数的方式是由用户输入的,因此message参数不能用于传递其他数据。


我正在处理一个非常令人沮丧的API,该API使用不带“ void * userarg”指针的回调例程。

假设使用API​​定义的使用回调例程的函数需要一个字符串参数(将由用户输入分配),是否有任何可能的方法可以在不使用全局变量的情况下将更多数据输入到我的回调例程中?

以下是API外观的简化示例:

#include <iostream>

using namespace std;

////////////////////////////////////////////////////////////////////////////////
// ASSUME EVERYTHING IN THIS SECTION IS PART OF AN API AND IS NOT MY OWN CODE...
// I DO NOT HAVE THE SOURCE AND IT CANNOT BE MODIFIED

typedef void (*CALLBACK)(string message);

void call_callback(CALLBACK cb) {

    // Gets a message from user input
    string message = "hello"; // pretend this is user input
    cb(message);
}

////////////////////////////////////////////////////////////////////////////////

int data = 42;

void callback_function(string message) {

    // I want to access "data" here WITHOUT it being global
    cout << message << ' ' << data << endl;
}

int main(int argc, char** argv) {
    call_callback(&callback_function);
}

通常,使用回调的API还将在回调例程中传递一个“ void * userarg”参数,因此您可以传递任何类型的其他数据,但这不是这种情况。

此API在我们的整个代码库中得到广泛使用,并且在每种情况下都必须100%传递更多数据。当前,我们通过 *准备屈服* 来获取更多数据的方式是,将几乎所有数据存储为单例,因此几乎所有内容都是全局的,并且可以从任何位置访问该程序。

整个概念对我来说似乎是 EVIL ,但是如果没有更好的API,我将无法找到任何更好的方法将数据导入回调。我已经联系了供应商,并要求他们修复其API以使其接受“ void * userarg”参数,但它似乎不会在不久的将来被修复...

我所希望的是比现在更好的做事方式。

3 个答案:

答案 0 :(得分:1)

如果它确实是一个std::string,它是回调的参数(而不是其他参数),并且您确实可以访问该参数(如示例代码中提供的字符串调用call_callback ),您可以将指向您分配的对象的实体序列化指针放入std::string(允许在其中包含任意数据),然后调用call_callback

这里的一个挑战是您将不得不手动管理该指针。

答案 1 :(得分:0)

我最直接的想法是提供唯一的字符串来代替您通常期望的void*。然后,您将有一个映射单例将字符串映射到您的回调。

是这样的:

class Dispatcher
{
public:
    // TODO: Thread safety etc.
    std::string makeCallback(std::function<void()> callback)
    {
        std::string uid = std::to_string(_index);
        _callbacks[uid] = std::move(callback);
        _index++;
        return uid;
    }

    void runCallback(std::string uid)
    {
        _callbacks[uid]();
    }

private:
    size_t _index = 0;
    std::map<std::string, std::function<void()>> _callbacks;
};

void leaveAPI(std::string uid)
{
    getSingleton<Dispatcher>()->runCallback(uid);
}

void enterAPI(std::function<void()> userCallback)
{
    std::string uid = getSingleton<Dispatcher>()->makeCallback(userCallback);
    call_callback(leaveAPI, uid);
}

Demo

您每次调用API时都只计算一个数字,并将其字符串版本用作回调参数。该类还将每个字符串映射到您要调用的回调。您可以删除映射条目或进行任何数量的性能优化,但这只是要点。

(如果您知道const char*出现的所有权/生命周期问题,这同样适用。)

答案 2 :(得分:-3)

您应该真正请API的编写者使用std::function而不是原始的函数指针。

然后,您可以轻松使用例如std::bindlambda expressions能够调用带有比回调更多参数的函数。

例如:

// The callback is a function taking one string argument, and return nothing
using CALLBACK = std::function<void(std::string)>;

// Do some processing and then call the callback function
void call_callback(CALLBACK cb, std::string message)
{
    // ...
    cb(message);
}

// Our callback takes a string *and* an integer argument
void callback_function(std::string message, int data)
{
    std::cout << message << ' ' << data << '\n';
}

int main()
{
    int local_data = 42;

    // Using std::bind...
    using namespace std::placeholders;  // for _1, _2, _3...
    call_callback(std::bind(&callback_function, _1, local_data), "Foobar");

    // Using lambdas...
    call_callback([local_data](std::string message)
    {
        callback_function(message, local_data);
    }, "Foobar");
}

使用std::function还可以轻松地将成员函数用作回调,不仅是非成员函数(或static成员函数)。


但是,如果您不能修改API,或者它的创建者不会更改它,并且必须使用指向非成员函数的C风格的纯指针,您仍然可以用lambda解决,但您无法捕获到lambda:

call_callback([/*empty!*/](std::string message)
{
    // Call the function as defined in the previous snippet
    callback_function(message, 42);  // Pass the value directly
}, "Foobar");