如何将C++lambda传递给需要函数指针和上下文的C回调

How can I pass a C++ lambda to a C-callback that expects a function pointer and a context?

本文关键字:指针 函数 上下文 回调 C++lambda      更新时间:2023-10-16

我正在尝试在使用标准函数-接口+上下文范例的C-API中注册回调。以下是api的样子:

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

我真正想做的是能够注册一个C++lambda作为回调。此外,我希望lambda是一个具有捕获变量(即不能转换为直接的无状态std::function)的lambda

我需要编写什么样的适配器代码才能将lambda注册为回调?

简单的方法是将lambda粘贴到保存在某个地方的std::function<void()>中。它可能是在堆上分配的,并且仅由向接受回调的实体注册的void*引用。回调将只是一个这样的函数:

extern "C" void invoke_function(void* ptr) {
    (*static_cast<std::function<void()>*>(ptr))();
}

注意,std::function<S>可以保存具有状态的函数对象,例如,具有非空捕获的lambda函数。你可以注册这样的回调:

register_callback(&invoke_function,
  new std::function<void()>([=](){ ... }));

最有效的方法是直接voidify lambda。

#include <iostream>
#include <tuple>
#include <memory>
template<class...Args>
struct callback {
  void(*function)(void*, Args...)=nullptr;
  std::unique_ptr<void, void(*)(void*)> state;
};
template<typename... Args, typename Lambda>
callback<Args...> voidify( Lambda&& l ) {
  using Func = typename std::decay<Lambda>::type;
  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.function, voidified.state.get() );
  register_callback( voidified.function, voidified.state.get() );
  std::cout << x << "n";
}
int main() {
  test();
}

这里,voidify接受一个lambda和(可选)一个参数列表,并生成一个传统的C风格回调-void*对。void*由具有特殊deleter的unique_ptr所有,因此其资源被正确清理。

std::function解决方案相比,它的优势在于效率——我消除了一个级别的运行时间接性。回调有效的生存期也很清楚,因为它在voidify返回的std::unique_ptr<void, void(*)(void*)>中。

如果您想要更复杂的生存期,unique_ptr<T,D>s可以是moved到shared_ptr<T>


以上内容将寿命与数据相结合,将类型擦除与实用性相结合。我们可以拆分:

template<class Lambda, class...Args>
struct callback {
  void(*function)(void*, Args...)=nullptr;
  Lambda state;
};
template<typename... Args, typename Lambda>
callback<typename std::decay<Lambda>::type, Args...> voidify( Lambda&& l ) {
  using Func = typename std::decay<Lambda>::type;
  return {
    +[](void* v, Args... args)->void {
      Func* f = static_cast< Func* >(v);
      (*f)(std::forward<Args>(args)...);
    },
    std::forward<Lambda>(l)
  };
}

现在voidify不分配。只需在回调的生存期内存储您的void,将一个指向-second的指针作为void*,并与first函数指针一起传递。

如果您需要在堆栈外存储此构造,将lambda转换为std::function可能会有所帮助。或者使用上面的第一个变体。

void test() {
  int x = 0;
  auto closure = [&]()->void { ++x; };
  auto voidified = voidify(closure);
  register_callback( voidified.function, &voidified.state );
  register_callback( voidified.function, &voidified.state );
  std::cout << x << "n";
}

lambda函数与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);

将lamda函数用作C函数指针的一种非常简单的方法是:

auto fun=+[](){ //add code here }

请参阅此答案作为示例。https://stackoverflow.com/a/56145419/513481