[英]How can I pass a C++ lambda to a C-callback that expects a function pointer and a context?
I'm trying to register a callback in a C-API that uses the standard function-pointer+context paradigm.我正在尝试在使用标准函数指针+上下文范例的 C-API 中注册回调。 Here's what the api looks like:
这是 api 的样子:
void register_callback(void(*callback)(void *), void * context);
What I'd really like to do is be able to register a C++ lambda as the callback.我真正想做的是能够将 C++ lambda 注册为回调。 Additionally, I want the lambda to be one that has captured variables (ie. can't be converted to a straight stateless
std::function
)此外,我希望 lambda 是一个已捕获变量的(即不能转换为直接无状态
std::function
)
What kind of adapter code would I need to write to be able to register a lambda as the callback?我需要编写什么样的适配器代码才能将 lambda 注册为回调?
The simple approach is to stick the lambda into a std::function<void()>
which is kept somewhere.简单的方法是将 lambda 粘贴到保存在某处的
std::function<void()>
中。 Potentially it is allocated on the heap and merely referenced by the void*
registered with the entity taking the callback.可能它是在堆上分配的,并且仅被注册到接受回调的实体的
void*
引用。 The callback would then simply be a function like this:回调将只是一个像这样的函数:
extern "C" void invoke_function(void* ptr) {
(*static_cast<std::function<void()>*>(ptr))();
}
Note that std::function<S>
can hold function objects with state, eg, lambda functions with a non-empty capture.请注意,
std::function<S>
可以保存具有状态的函数对象,例如,具有非空捕获的 lambda 函数。 You could register a callback like this:您可以像这样注册回调:
register_callback(&invoke_function,
new std::function<void()>([=](){ ... }));
The most efficient way is to voidify
the lambda directly.最有效的方法是直接使 lambda
voidify
。
#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();
}
here voidify
takes a lambda and (optionally) a list of arguments, and generates a traditional C-style callback- void*
pair.这里
voidify
接受一个 lambda 表达式和(可选)一个参数列表,并生成一个传统的 C 风格回调void*
对。 The void*
is owned by a unique_ptr
with a special deleter so its resources are properly cleaned up. void*
由具有特殊删除器的unique_ptr
拥有,因此其资源被正确清理。
The advantage of this over a std::function
solution is efficiency -- I eliminated one level of run-time indirection.与
std::function
解决方案相比,它的优势在于效率——我消除了一层运行时间接。 The lifetime that the callback is valid is also clear, in that it is in the std::unique_ptr<void, void(*)(void*)>
returned by voidify
.回调有效的生命周期也很清楚,因为它在
voidify
返回的std::unique_ptr<void, void(*)(void*)>
。
unique_ptr<T,D>
s can be move
d into shared_ptr<T>
if you want a more complex lifetime.如果您想要更复杂的生命周期
unique_ptr<T,D>
可以将unique_ptr<T,D>
s move
到shared_ptr<T>
中。
The above mixes lifetime with data, and type erasure with utility.上面将生命周期与数据混合在一起,将类型擦除与实用程序混合在一起。 We can split it:
我们可以拆分它:
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)
};
}
Now voidify
does not allocate.现在
voidify
不分配。 Simply store your voidify for the lifetime of the callback, passing a pointer-to- second
as your void*
along side the first
function pointer.只需在回调的生命周期内存储您的 voidify,将指向
second
的指针作为您的void*
与first
函数指针一起传递。
If you need to store this construct off the stack, converting the lambda to a std::function
may help.如果您需要将此构造存储在堆栈外,将 lambda 转换为
std::function
可能会有所帮助。 Or use the first variant above.或者使用上面的第一个变体。
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";
}
A lambda function is compatible with C-callback function as long as it doesn't have capture variables.只要 lambda 函数没有捕获变量,它就与 C 回调函数兼容。
Force to put something new to old one with new way doesn't make sense.强行用新的方式把新的东西放到旧的东西上是没有意义的。
How about following old-fashioned way?如何遵循老式的方式?
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);
A very simple way to get use a lamda function as a C funtion pointer is this:将 lamda 函数用作 C 函数指针的一种非常简单的方法是:
auto fun=+[](){ //add code here }
see this answer for a example.请参阅此答案以获取示例。 https://stackoverflow.com/a/56145419/513481
https://stackoverflow.com/a/56145419/513481
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.