如何通过 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,并且回收池需要是线程安全的。