Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I pass a std::function object to a function taking a function pointer?

Tags:

c++

c

I am trying to interface with a library written in c, that uses this familiar pattern:

void some_c_handler(void(*func)(void*), void* data);

Now, I want to write a C++ wrapper for this function that looks like this:

void my_new_cpp_handler(std::function<void()>&& func)
{
  void (*p)() = foo(func);
  void* data = bar(func);
  some_c_handler(p, data);
}

Both some_c_handler and my_new_cpp_handler are solving the same problem; they're taking in some kind of function along with some state. But the latter is preferred in that it abstracts much of the implementation details from the user, and allows for simply passing in a lambda object.

So my_new_cpp_handler should take the func parameter it is given, convert it to a function pointer and pass its state on to data.

I don't know enough about the standard, or the implementation of std::function to know if this is even a reasonable request. Can foo and bar exist?

To put it differently, what I want is to be able to pass a stateful function to a c callback handler without having to manually instantiate my own struct type to pass along with it. Obviously std::function has already done this for us, so I'd love to be able to separate the function pointer from the state somehow and pass it onto the c-style handler. Is this possible?

like image 475
quant Avatar asked Sep 18 '16 20:09

quant


3 Answers

Is this possible?

No.

You can wrap a C-style callback into an std::function<>..., and the compiler will emit code to extract the data and the handler and call it. However, the exact code to do so will depend on the compiler, the ABI and the standard C++ library being used. You can't magically reconstruct that code given only the std::function wrapper.

Further, an arbitrary std::function... (or a lambda) may wrap code other than a call to C-style handler. It should be obvious that applying the "magically reconstructed" code to extract C-style handler from such an instance of std::function... can't possibly succeed.

P.S.

To put it differently, what I want is to be able to pass a stateful function to a c callback handler without having to manually instantiate my own struct type to pass along with it. Obviously std::function has already done this for us

So why don't you just use what std::function has already done for you:

void my_new_cpp_handler(std::function<void()>&& func)
{
  func();  // calls some_c_handler(p, data) IFF that is what "func" encodes.
}
like image 158
Employed Russian Avatar answered Oct 10 '22 19:10

Employed Russian


You can make a wrapper function whose purpose is to simply execute the std::function callback.

void some_c_handler(void(*)(void*), void*) {}

void std_function_caller(void* fn) { 
    (*static_cast<std::function<void()>*>(fn))();
};

auto make_std_function_caller(std::function<void()>& fn) { 
    return std::make_pair(std_function_caller, static_cast<void*>(&fn));
}

void my_new_cpp_handler(std::function<void()>&& func) { 
    const auto p = make_std_function_caller(func);
    some_c_handler(p.first, p.second);
}
like image 23
Brian Bi Avatar answered Oct 10 '22 17:10

Brian Bi


If you were to carefully review the documentation for this library, you will find that the

void some_c_handler(void(*func)(void*), void* data);

Invokes func, passing it the data argument.

This is a very common design pattern for C libraries that take a callback function. In addition to the callback function, they also take an additional opaque pointer that is not interpreted by the library, but is blindly forwarded to the func. In other words, the C library invokes

 func(data);

You can use this from C++ code to pass an ordinary pointer to any class.

This includes std::function, too.

The trick is that in most situations it will be necessary to use new:

auto *pointer=new std::function< function_type >...

The end result is a pointer that can be passed to the C library, together with a pointer to a "trampoline function":

some_c_handler(&cpp_trampoline, reinterpret_cast<void *>(pointer));

And the trampoline recasts the opaque pointer:

void cpp_trampoline(void *pointer)
{
   auto real_pointer=reinterpret_cast<std::function< ... >*>(pointer);

   // At this point, you have a pointer to the std::function here.
   // Do with it as you wish.

The only detail you will need to square away here is to figure out the correct scope for the dynamically-allocated function pointer, in order to avoid memory leaks.

like image 7
Sam Varshavchik Avatar answered Oct 10 '22 19:10

Sam Varshavchik