How to pass a std::function with capture through a C API?

Viewed 1004

I have a C API that is a queue used to pass messages between threads. I want to pass a std::function<void()> through it, but to do that, I need to degrade it to a POD chunk of data of constant length.

The std::function will be mostly from C++11 lambdas and will capture by reference or copy. I can use the heap from both sides of the C-queue.

The queue itself is a FreeRTOS queue and this is in embedded. There is some discussion about passing C++ish things through the queue on their forums. It mostly says it's ok if it's a POD or is trivially constructable.

Currently I'm passing around struct { void (*fp)(void*); void* context; }, and execute it as received.fp(received.context); but I'd like something a bit better in terms of readability without sacrificing much more resources. (EDIT: expanded on current use and needs)

Any idea how to do this?

3 Answers

You can pass a pointer to your std::function<void()>. Pointers are POD. You can use casts to enqueue/dequeuer, and use heap i.e. new/delete to allocate/deallocate.

However, if that’s embedded and you have large rate of these messages (I’d say more than 100-1000 messages per second), it’s not necessary a good idea to stress memory manager this way. If that’s your case, I’d reworked the messages so they don’t use dynamic memory, allocated an adequately-sized pool for the in-flight messages (if you have 1 consumer and 1 producer, the adequate size is probably RTOS queue length + 2), and used something to recycle the message pointers back to the pool once your consumer thread[s] consumed a message.

Unfortunately this increases complexity a lot. No dynamic memory means no lambdas, and that recycling pool needs to be thread-safe.

I am first going to solve your problem exactly, then show you the way to solve it in a generic way that will work for many different 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
  };
}

this takes a pointer to a std::function (or a lambda, I don't care), and creates a tuple of pointer-to-void and pointer-to-function.

Invoking pointer-to-function on pointer-to-void calls the std::function (or lambda).


This is the generic solution:

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);
}

in your case, you want c_wrap<void()>(&some_std_function). From that, you can access .ptr and .f to pass to your C API.

It doesn't manage memory at all.

You can pass additional args or handle return values by changing the void() signature; however, it assumes the void* "self" component is the first argument. A fancier version can support the void* anywhere, but that gets verbose.

In case you are wondering how this works, a stateless lambda can be converted to a functoin pointer, which is what we use in the c_api constructor above.

I don't know much about FreeRTOS, but with your requirements, you should be able to use a pointer to your std::function object (either heap-allocated or a pointer to a non-heap allocated object if its lifetime permits) -- all raw pointers are POD, even if they point to C++ objects.

Here's a quick example of using a pointer to a bound std::function allocated on the stack:

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;
}
Related