如何通过C API传递带捕获的std :: function?

时间:2017-11-27 17:25:43

标签: c++ c++11 lambda std-function

我有一个C API,它是一个用于在线程之间传递消息的队列。我希望通过它传递一个std::function<void()>,但为了做到这一点,我需要将它降级为POD的恒定长度的数据块。

std::function主要来自C ++ 11 lambdas,将通过引用或复制进行捕获。我可以使用来自C队列两侧的堆。

队列本身是一个FreeRTOS队列,它是嵌入式的。他们的论坛上有一些discussion about passing C++ish things through the queue。如果它是POD或可以轻松构建,它通常会说它没问题。

目前我正在struct { void (*fp)(void*); void* context; }传递,并以received.fp(received.context)执行;但是在不牺牲更多资源的情况下,我在可读性方面更喜欢一些东西。 (编辑:扩大了当前的使用和需求)

知道怎么做吗?

3 个答案:

答案 0 :(得分:3)

您可以将指针传递给std::function<void()>。指针是POD。您可以使用强制转换来排队/取消,并使用堆即新建/删除来分配/解除分配。

但是,如果这是嵌入的并且你有大量的这些消息(我说每秒超过100-1000条消息),那么以这种方式强调内存管理器并不是一个好主意。如果是这种情况,我会重新编写消息,以便它们不使用动态内存,为正在进行的消息分配一个足够大小的池(如果你有1个消费者和1个生产者,那么足够的大小可能是RTOS队列长度+ 2),并在消费者线程消费消息后使用一些东西将消息指针回收到池中。

不幸的是,这增加了很多复杂性。没有动态内存意味着没有lambdas,并且回收池需要是线程安全的。

答案 1 :(得分:1)

我首先要完全解决您的问题,然后向您展示以通用方式解决问题的方法,该方法适用于许多不同的C API。

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()签名来传递其他参数或处理返回值;然而,它假设void*&#34; self&#34; component是第一个参数。 void*,但这会变得冗长。

如果您想知道它是如何工作的,可以将无状态lambda转换为functoin指针,这是我们在上面的c_api构造函数中使用的。

答案 2 :(得分:0)

我对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;
}