Передача произвольных данных в обратный вызов C ++, который не принимает «void * userarg» - PullRequest
0 голосов
/ 02 июля 2018

EDIT:

Я изменил пример API, чтобы он лучше отражал реальный API, с которым я имею дело. API получает аргумент message, передаваемый ему, путем ввода данных пользователем, поэтому аргумент message нельзя использовать для передачи дополнительных данных.


Я имею дело с очень разочаровывающим 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% необходимо передавать намного больше данных в каждом случае, когда он используется. Текущий способ получения большего количества данных в * подготовка к сжатию * заключается в хранении практически всех наших данных в единичных блоках, так что почти все является глобальным и к нему можно получить доступ буквально из любой точки программы .

Вся эта концепция кажется мне ЗЛО , но без лучшего API я не могу найти лучшего способа получить данные для обратного вызова. Я уже связался с поставщиком и попросил, чтобы он исправил свой API, чтобы он принимал аргумент "void * userarg", но не похоже, что он будет исправлен в ближайшее время ...

Все, на что я надеюсь, это ЛЮБОЙ лучший способ делать вещи, чем мы сейчас.

Ответы [ 3 ]

0 голосов
/ 02 июля 2018

Если это действительно std::string, который является аргументом для обратного вызова (а не чего-то еще), и у вас действительно есть доступ к аргументу (как в вашем примере кода, который вызывает call_callback с предоставленной строкой), вы можете поместить сериализованный по сущности указатель на ваш выделенный объект в std::string (который может содержать произвольные данные) и вызовет call_callback с ним.

Одной из проблем здесь будет тот факт, что вам придется вручную управлять этим указателем.

0 голосов
/ 02 июля 2018

Моя самая простая идея - предоставить уникальные строки вместо void*, которые вы обычно ожидаете. Тогда у вас будет один map singleton, который сопоставляет строки с вашими обратными вызовами.

Так что-то вроде этого:

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);
}

Демо

Вы просто подсчитываете число каждый раз, когда вызываете API, и используете его строковую версию в качестве параметра обратного вызова. Класс также сопоставляет каждую из этих строк с обратным вызовом, который вы хотели вызвать. Вы можете удалить записи карты или выполнить любое количество оптимизаций производительности, но это суть.

(Это будет работать так же хорошо с const char*, если вы выясните, какие вопросы о владении / сроке службы будут открыты.)

0 голосов
/ 02 июля 2018

Вы действительно должны обратиться к авторам API с просьбой использовать std::function вместо необработанных указателей на функции.

Тогда вы можете легко использовать, например, std::bind или лямбда-выражения для возможности вызова функций, принимающих больше аргументов, чем для обратного вызова.

Например:

// 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 для функций, не являющихся членами, вы все равно можете решить это с лямбдами, но вы не можете иметь никаких захватов для лямбды:

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