如何通过 C API 传递带捕获的 std::function?
How to pass a std::function with capture through a C API?
我有一个 C API,它是一个用于在线程之间传递消息的队列。我想通过它传递一个 std::function<void()>
,但为此,我需要将它降级为一个固定长度的 POD 数据块。
std::function
将主要来自 C++11 lambda 并将通过引用或复制捕获。我可以使用 C 队列两侧的堆。
队列本身是一个 FreeRTOS 队列,并且是嵌入式的。他们的论坛上有一些 discussion about passing C++ish things through the queue。它主要是说如果它是 POD 或可以简单构造就可以了。
目前我正在绕过struct { void (*fp)(void*); void* context; }
,并以received.fp(received.context)
的形式执行;但我希望在不牺牲更多资源的情况下在可读性方面做得更好。 (编辑:扩展了当前的使用和需求)
知道怎么做吗?
我首先要准确地解决您的问题,然后向您展示以适用于许多不同 C APIs 的通用方式解决问题的方法。
struct c_api {
void(*f)(void*);
void* ptr;
};
template<class F>
c_api c_api_wrap( F* f ) {
return {
[](void* pv) { (*static_cast<F*>(pv))(); },
f
};
}
这需要一个指向 std::function
(或 lambda,我不在乎)的指针,并创建一个指向 void 的指针和指向函数的指针的元组。
在指向 void 的指针上调用指向函数的指针会调用 std::function
(或 lambda)。
这是通用解决方案:
template<class Sig>
struct c_api;
template<class R, class...Args>
struct c_api<R(Args...)> {
using fptr = R(*)(void*, Args...);
fptr f = nullptr;
void* ptr = nullptr;
template<class F>
explicit c_api( F* pf ):
f([](void* vptr, Args...args)->R {
return (*static_cast<F*>(vptr))(std::forward<Args>(args)...);
}),
ptr(pf)
{}
};
// not needed unless you want to pass in a non-void returning
// function to a void-returning C API:
template<class...Args>
struct c_api<void(Args...)> {
using fptr = void(*)(void*, Args...);
fptr f = nullptr;
void* ptr = nullptr;
template<class F>
explicit c_api( F* pf ):
f([](void* vptr, Args...args) {
(*static_cast<F*>(vptr))(std::forward<Args>(args)...);
}),
ptr(pf)
{}
};
// wrap a pointer to an arbitrary function object to be consumed
// by a C API:
template<class Sig, class F>
c_api<Sig> c_wrap( F* f ) {
return c_api<Sig>(f);
}
在您的情况下,您需要 c_wrap<void()>(&some_std_function)
。从那里,您可以访问 .ptr
和 .f
以传递给您的 C API.
它根本不管理内存。
您可以通过更改 void()
签名来传递额外的参数或处理 return 值;但是,它假定 void*
"self" 组件是第一个参数。更高级的版本可以在任何地方支持 void*
,但是会变得冗长。
如果您想知道这是如何工作的,可以将无状态 lambda 转换为函数指针,这就是我们在上面的 c_api
构造函数中使用的指针。
我不太了解 FreeRTOS,但根据您的要求,您应该能够使用指向 std::function
对象的指针(堆分配对象或非堆分配对象的指针如果它的生命周期允许的话)——所有原始指针都是 POD,即使它们指向 C++ 对象。
下面是一个使用指向在堆栈上分配的绑定 std::function
的指针的简单示例:
std::function<void()> produce(int value)
{
return [value]() {
std::cout << "Value = " << value << std::endl;
};
}
void consume(std::function<void()> *callback)
{
(*callback)();
}
int main()
{
auto cb = produce(42);
consume(&cb);
return 0;
}
您可以将指针传递给您的 std::function<void()>
。指针是 POD。您可以对 enqueue/dequeuer 使用强制转换,并使用堆,即 new/delete 到 allocate/deallocate。
但是,如果它是嵌入式的并且你有大量的消息(我会说每秒超过 100-1000 条消息),那么以这种方式强调内存管理器并不是一个好主意。如果是你的情况,我会重新处理消息,使它们不使用动态内存,为传输中的消息分配一个足够大小的池(如果你有 1 个消费者和 1 个生产者,足够的大小可能是 RTOS 队列长度+ 2),并在你的消费者线程[s]消费了一条消息后,使用一些东西将消息指针回收回池中。
不幸的是,这大大增加了复杂性。没有动态内存意味着没有 lambda,并且回收池需要是线程安全的。
我有一个 C API,它是一个用于在线程之间传递消息的队列。我想通过它传递一个 std::function<void()>
,但为此,我需要将它降级为一个固定长度的 POD 数据块。
std::function
将主要来自 C++11 lambda 并将通过引用或复制捕获。我可以使用 C 队列两侧的堆。
队列本身是一个 FreeRTOS 队列,并且是嵌入式的。他们的论坛上有一些 discussion about passing C++ish things through the queue。它主要是说如果它是 POD 或可以简单构造就可以了。
目前我正在绕过struct { void (*fp)(void*); void* context; }
,并以received.fp(received.context)
的形式执行;但我希望在不牺牲更多资源的情况下在可读性方面做得更好。 (编辑:扩展了当前的使用和需求)
知道怎么做吗?
我首先要准确地解决您的问题,然后向您展示以适用于许多不同 C APIs 的通用方式解决问题的方法。
struct c_api {
void(*f)(void*);
void* ptr;
};
template<class F>
c_api c_api_wrap( F* f ) {
return {
[](void* pv) { (*static_cast<F*>(pv))(); },
f
};
}
这需要一个指向 std::function
(或 lambda,我不在乎)的指针,并创建一个指向 void 的指针和指向函数的指针的元组。
在指向 void 的指针上调用指向函数的指针会调用 std::function
(或 lambda)。
这是通用解决方案:
template<class Sig>
struct c_api;
template<class R, class...Args>
struct c_api<R(Args...)> {
using fptr = R(*)(void*, Args...);
fptr f = nullptr;
void* ptr = nullptr;
template<class F>
explicit c_api( F* pf ):
f([](void* vptr, Args...args)->R {
return (*static_cast<F*>(vptr))(std::forward<Args>(args)...);
}),
ptr(pf)
{}
};
// not needed unless you want to pass in a non-void returning
// function to a void-returning C API:
template<class...Args>
struct c_api<void(Args...)> {
using fptr = void(*)(void*, Args...);
fptr f = nullptr;
void* ptr = nullptr;
template<class F>
explicit c_api( F* pf ):
f([](void* vptr, Args...args) {
(*static_cast<F*>(vptr))(std::forward<Args>(args)...);
}),
ptr(pf)
{}
};
// wrap a pointer to an arbitrary function object to be consumed
// by a C API:
template<class Sig, class F>
c_api<Sig> c_wrap( F* f ) {
return c_api<Sig>(f);
}
在您的情况下,您需要 c_wrap<void()>(&some_std_function)
。从那里,您可以访问 .ptr
和 .f
以传递给您的 C API.
它根本不管理内存。
您可以通过更改 void()
签名来传递额外的参数或处理 return 值;但是,它假定 void*
"self" 组件是第一个参数。更高级的版本可以在任何地方支持 void*
,但是会变得冗长。
如果您想知道这是如何工作的,可以将无状态 lambda 转换为函数指针,这就是我们在上面的 c_api
构造函数中使用的指针。
我不太了解 FreeRTOS,但根据您的要求,您应该能够使用指向 std::function
对象的指针(堆分配对象或非堆分配对象的指针如果它的生命周期允许的话)——所有原始指针都是 POD,即使它们指向 C++ 对象。
下面是一个使用指向在堆栈上分配的绑定 std::function
的指针的简单示例:
std::function<void()> produce(int value)
{
return [value]() {
std::cout << "Value = " << value << std::endl;
};
}
void consume(std::function<void()> *callback)
{
(*callback)();
}
int main()
{
auto cb = produce(42);
consume(&cb);
return 0;
}
您可以将指针传递给您的 std::function<void()>
。指针是 POD。您可以对 enqueue/dequeuer 使用强制转换,并使用堆,即 new/delete 到 allocate/deallocate。
但是,如果它是嵌入式的并且你有大量的消息(我会说每秒超过 100-1000 条消息),那么以这种方式强调内存管理器并不是一个好主意。如果是你的情况,我会重新处理消息,使它们不使用动态内存,为传输中的消息分配一个足够大小的池(如果你有 1 个消费者和 1 个生产者,足够的大小可能是 RTOS 队列长度+ 2),并在你的消费者线程[s]消费了一条消息后,使用一些东西将消息指针回收回池中。
不幸的是,这大大增加了复杂性。没有动态内存意味着没有 lambda,并且回收池需要是线程安全的。