Подтвердить что ты не робот

Как передать lambda С++ на C-обратный вызов, который ожидает указатель на функцию и контекст?

Я пытаюсь зарегистрировать обратный вызов в C-API, который использует стандартную парадигму function-pointer + context. Вот как выглядит api:

void register_callback(void(*callback)(void *), void * context);

То, что я действительно хотел бы сделать, это зарегистрировать С++ лямбда в качестве обратного вызова. Кроме того, я хочу, чтобы лямбда была той, которая захватила переменные (т.е. Не может быть преобразована в прямой апатрид std::function)

Какой код адаптера мне нужно написать, чтобы иметь возможность регистрировать лямбда в качестве обратного вызова?

4b9b3361

Ответ 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/...