Как передать lambda С++ на C-обратный вызов, который ожидает указатель на функцию и контекст?
Я пытаюсь зарегистрировать обратный вызов в C-API, который использует стандартную парадигму function-pointer + context. Вот как выглядит api:
void register_callback(void(*callback)(void *), void * context);
То, что я действительно хотел бы сделать, это зарегистрировать С++ лямбда в качестве обратного вызова. Кроме того, я хочу, чтобы лямбда была той, которая захватила переменные (т.е. Не может быть преобразована в прямой апатрид std::function
)
Какой код адаптера мне нужно написать, чтобы иметь возможность регистрировать лямбда в качестве обратного вызова?
Ответы
Ответ 1
Простой aporoach - вставить лямбду в std::function<void()>
которая где-то хранится. Потенциально он размещается в куче и просто ссылается на void*
зарегистрированную у объекта, принимающего обратный вызов. Обратный вызов тогда будет просто функцией, подобной этой:
extern "C" void invoke_function(void* ptr) {
(*static_cast<std::function<void()>*>(ptr))();
}
Обратите внимание, что std::function<S>
может содержать функциональные объекты с состоянием, например лямбда-функции с непустым захватом. Вы можете зарегистрировать обратный вызов, как это:
register_callback(&invoke_function,
new std::function<void()>([=](){ ... }));
Ответ 2
Самый эффективный способ состоит в том, чтобы voidify
лямбду напрямую.
#include <iostream>
#include <tuple>
#include <memory>
template<typename... Args, typename Lambda>
std::pair< void(*)(void*, Args...), std::unique_ptr<void, void(*)(void*)> > voidify( Lambda&& l ) {
typedef typename std::decay<Lambda>::type Func;
std::unique_ptr<void, void(*)(void*)> data(
new Func(std::forward<Lambda>(l)),
+[](void* ptr){ delete (Func*)ptr; }
);
return {
+[](void* v, Args... args)->void {
Func* f = static_cast< Func* >(v);
(*f)(std::forward<Args>(args)...);
},
std::move(data)
};
}
void register_callback( void(*function)(void*), void * p ) {
function(p); // to test
}
void test() {
int x = 0;
auto closure = [&]()->void { ++x; };
auto voidified = voidify(closure);
register_callback( voidified.first, voidified.second.get() );
register_callback( voidified.first, voidified.second.get() );
std::cout << x << "\n";
}
int main() {
test();
}
здесь voidify
принимает лямбду и (необязательно) список аргументов и генерирует традиционную пару callback- void*
стиле C. void*
принадлежит unique_ptr
со специальным удалителем, поэтому его ресурсы должным образом очищены.
Преимущество этого решения перед std::function
заключается в эффективности - я исключил один уровень косвенности во время выполнения. Время жизни, что обратный вызов действителен, также ясно, потому что оно находится в std::unique_ptr<void, void(*)(void*)>
возвращаемом voidify
.
unique_ptr<T,D>
можно move
d в shared_ptr<T>
если вы хотите более сложное время жизни.
Вышеприведенное смешивает время жизни с данными и стирание типа с утилитой. Мы можем разделить это:
template<typename... Args, typename Lambda>
std::pair< void(*)(void*, Args...), std::decay_t<Lambda> > voidify( Lambda&& l ) {
typedef typename std::decay<Lambda>::type Func;
return {
+[](void* v, Args... args)->void {
Func* f = static_cast< Func* >(v);
(*f)(std::forward<Args>(args)...);
},
std::forward<Lambda>(l)
};
}
Теперь voidify
не выделяет. Просто сохраните ваш voidify на весь срок действия обратного вызова, передавая указатель на second
как void*
вдоль first
указателя функции.
Если вам нужно сохранить эту конструкцию вне стека, может помочь преобразование лямбда-функции в std::function
. Или используйте первый вариант выше.
Ответ 3
Лямбда-функция совместима с функцией обратного вызова C, если у нее нет переменных захвата.
Сила, чтобы положить что-то новое в старое с новым путем, не имеет смысла.
Как насчет следующего старомодного пути?
typedef struct
{
int cap_num;
} Context_t;
int cap_num = 7;
Context_t* param = new Context_t;
param->cap_num = cap_num; // pass capture variable
register_callback([](void* context) -> void {
Context_t* param = (Context_t*)context;
std::cout << "cap_num=" << param->cap_num << std::endl;
}, param);
Ответ 4
Очень простой способ использовать функцию lamda в качестве указателя на функцию C:
auto fun=+[](){ //add code here }
см. этот ответ для примера. fooobar.com/info/52534/...