Ivan Lebediev
Ivan Lebediev

Reputation: 537

Is it possible to bind() *this to class member function to make a callback to C API

Is there a way to use boost or std bind() so I could use a result as a callback in C API? Here's sample code I use:

#include <boost/function.hpp>
#include <boost/bind/bind.hpp>

typedef void (*CallbackType)();

void CStyleFunction(CallbackType functionPointer)
{
    functionPointer();
}

class Class_w_callback
{
public:
    Class_w_callback()
    {
        //This would not work
    CStyleFunction(boost::bind(&Class_w_callback::Callback, this));
    }
    void Callback(){std::cout<<"I got here!\n";};
};

Thanks!

Upvotes: 4

Views: 2110

Answers (4)

Alexis Wilke
Alexis Wilke

Reputation: 20798

Globals

As mentioned by the others, you need a global (a static member is a global hidden as a variable member) and of course if you need multiple objects to make use of different parameters in said callback, it won't work.

Context Parameters in Callback

A C library may offer a void * or some similar context. In that case use that feature.

For example, the ffmpeg library supports a callback to read data which is defined like so:

int(*read_packet)(void *opaque, uint8_t *buf, int buf_size);

The opaque parameter can be set to this. Within your callback, just cast it back to your type (name of your class).

Library Context Parameter in Calback

A C library may call your callback with its object (struct pointer). Say you have a library named example which offers a type named example_t and defines callbacks like this:

callback(example_t *e, int param);

Then you may be able to place your context (a.k.a. this pointer) in that example_t structure and retrieve it back out in your callback.

Serial Calls

Assuming you have only one thread using that specific C library and that the callback can only be triggered when you call a function in the library (i.e. you do not get events triggered at some random point in time,) you could still use a global variable. What you have to do is save your current object in the global before each call. Something like this:

object_i_am_working_with = this;
make_a_call_to_that_library();

This way, inside the callback you can always access the object_i_am_working_with pointer. This does not work in a multithreaded application or when the library automatically generates events in the background (i.e. a key press, a packet from the network, a timer, etc.)

One Thread Per Object (since C++11)

This is an interesting solution in a multi-threaded environment. When none of the previous solutions are available to you, you may be able to resolve the problem using threads.

In C++11, there is a new special specifier named thread_local. In the old days, you had to handle that by hand which would be specific to each thread implementation... now you can just do this:

thread_local Class_w_callback * callback_context = nullptr;

Then when in your callback you can use the callback_context as the pointer back to your Class_w_callback class.

This, of course, means you need to create one thread per object you create. This may not be feasible in your environment. In my case, I have components which are all running their own loop and thus each have their own thread_local environment.

Note that if the library automatically generates events you probably can't do that either.

Old Way with Threads (And C solution)

As I mentioned above, in the old days you would have to manage the local thread environment yourself. With pthread (Linux based), you have the thread specific data accessed through pthread_getspecific():

void *pthread_getspecific(pthread_key_t key);
int pthread_setspecific(pthread_key_t key, const void *value);

This makes use of dynamically allocated memory. This is probably how the thread_local is implemented in g++ under Linux.

Under MS-Windows, you probably would use the TlsAlloc function.

Upvotes: 1

user4815162342
user4815162342

Reputation: 155296

You cannot do this in portable C++. However, there are libraries out there that enable creation of C functions that resemble closures. These libraries include assembly code in their implementation and require manual porting to new platforms, but if they support architectures you care about, they work fine.

For example, using the trampoline library by Bruno Haible, you would write the code like this:

extern "C" {
#include <trampoline.h>
}

#include <iostream>

typedef int (*callback_type)();

class CallbackDemo {
  static CallbackDemo* saved_this;
public:
  callback_type make_callback() {
    return reinterpret_cast<callback_type>(
      alloc_trampoline(invoke, &saved_this, this));
  }

  void free_callback(callback_type cb) {
    free_trampoline(reinterpret_cast<int (*)(...)>(cb));
  }

  void target(){
    std::cout << "I got here, " << this << '\n';
  };

  static int invoke(...) {
    CallbackDemo& me = *saved_this;
    me.target();
    return 0;
  }
};

CallbackDemo *CallbackDemo::saved_this;

int main() {
  CallbackDemo x1, x2;
  callback_type cb1 = x1.make_callback();
  callback_type cb2 = x2.make_callback();
  cb1();
  cb2();
}

Note that, despite the use of a static member, the trampolines created by alloc_trampoline are reentrant: when the returned callback is invoked, it first copies the pointer to the designated address, and then invokes the original function with original arguments. If the code must also be thread-safe, saved_this should be made thread-local.

Upvotes: 3

ComicSansMS
ComicSansMS

Reputation: 54727

This won't work.

The problem is that bind returns a functor, that is a C++ class with an operator() member function. This will not bind to a C function pointer. What you need is a static or non-member function that stores the this pointer in a global or static variable. Granted, finding the right this pointer for the current callback might be a non-trivial task.

Upvotes: 2

Stack Overflow is garbage
Stack Overflow is garbage

Reputation: 248199

No, there is no way to do that. The problem is that a C function pointer is fundamentally nothing more than an instruction address: "go to this address, and execute the instructions you find". Any state you want to bring into the function has to either be global, or passed as parameters.

That is why most C callback APIs have a "context" parameter, typically a void pointer, that you can pass in, and just serves to allow you to pass in the data you need.

Upvotes: 5

Related Questions