6

I have a C library function that expects a function pointer for callback, and I want to pass in a C++ member function. The C++ function modifies a member variable, so I can't use a static free function (as suggested in several similar posts). My attempt (shown below) fails with a compiler error.

This post comes closest to what I need:

Using a C++ class member function as a C callback function

How can I do this without static functions? Thanks!


test.h

#ifndef TEST_H_
#define TEST_H_

#ifdef __cplusplus
extern "C" {
#endif

typedef void (*handler_t)(int foo, void *bar);

void set_handler(handler_t h);

#ifdef __cplusplus
}
#endif

#endif

test.c

#include "test.h"
#include <stdlib.h>

static handler_t handler_ = NULL;
void set_handler(handler_t h) {
        handler_ = h;
}

void handle_event(int foo, void *bar) {
        if (handler_ != NULL) handler_(foo, bar);
}

test.cpp

#include "test.h"
#include <iostream>
using namespace std;

class Foo {
public:
        Foo() : ctr_(0) {};

        // handler needs to access non-static variable, so it can't be static
        void handler(int foo, void *bar) { ++ctr_;  }

private:
        int ctr_;
};

int main(int argc, char **argv) {
        // error: can't convert to "void (*)(int, void*)"
        set_handler(&Foo::handler);

        cout << "done" << endl;
        return 0;
}

GCC barf

$ gcc test.cpp test.c 
test.cpp: In function ‘int main(int, char**)’: 
test.cpp:18: error: cannot convert ‘void (Foo::*)(int, void*)’ to ‘void (*)(int, void*)’ for argument ‘1’ to ‘void set_handler(void (*)(int, void*))’
Community
  • 1
  • 1
  • Can you explain what the parameters to the handler function are/mean? Do you get to specify what either of those are in a separate setter or anything? – Walter Mundt Feb 03 '11 at 01:47
  • [Why this is a problem](http://stackoverflow.com/questions/1707575/c-static-function-wrapper-that-routes-to-member-function/1707678#1707678) – Martin York Feb 03 '11 at 01:48
  • What exactly gets passed to your `handler` function? Can you maybe change the signature a callback handler must have or are you bound to use what is provided by that library? – Xeo Feb 03 '11 at 01:49
  • @Walter, @Xeo: I meant to convey that the parameters to `handler` are irrelevant by naming them "foo" and "bar" (they're just example parameters...having no parameters gives the same error). Yes, I can change the signature of `handler_t`. –  Feb 03 '11 at 02:00
  • Then I suggest you to use vz0's answer, passing in a `Foo` instance as the context. At least that's how all C-callbacks I've ever seen work, they allow you to pass in your own parameter as a `void*` – Xeo Feb 03 '11 at 02:05
  • possible duplicate of [null pointer when getting function pointer using boost::function::target](http://stackoverflow.com/questions/1381042/null-pointer-when-getting-function-pointer-using-boostfunctiontarget) – Omnifarious Feb 03 '11 at 02:26
  • See https://stackoverflow.com/questions/1000663/using-a-c-class-member-function-as-a-c-callback-function/56943930#56943930 – cibercitizen1 Jul 09 '19 at 06:57

6 Answers6

8

It is not possible, at least with that handler_t signature.

While you can create a free function on your .cpp to wrap the member call, you need a pointer to the Foo instance:

void my_wrap(int foo, void* bar) {
    Foo* some_foo_instance = ...;
    some_foo_instance->handler(foo, bar);
}

int main(int argc, char **argv) {
    set_handler(&my_wrap);
}

You need some void* to pass the Foo instance as a handler attribute:

// Header
typedef void (*handler_t)(int foo, void *bar, void* arg1);
void set_handler(handler_t h, void* arg1);

// Impl.
void set_handler(handler_t h, void* arg1) {
        handler_ = h;
        handler_arg1_ = arg1;
}

// cpp
void my_wrap(int foo, void* bar, void* arg1) {
    Foo* some_foo_instance = static_cast<Foo*>(arg1);
    some_foo_instance->handler(foo, bar);
}

// main
int main(int argc, char **argv) {
    Foo some_concrete_instance;
    set_handler(&my_wrap, static_cast<void*>(&some_concrete_instance));
}
vz0
  • 30,291
  • 7
  • 37
  • 74
3

The big question is how many times you need to call set_handler multiple times to call methods on different objects. If this answer is one, you can do something like this:

#include <boost/function.hpp>

class HandlerContext
{
    static boost::function<void (int, void*)> s_func

    static void forward(int foo, void* bar)
    {
         s_func(foo, bar);
    }

public:
    static void set(boost::function<int, void*> const& f)
    {
        s_func = f;
        set_handler(&HandlerContext::forward);
    }
};

If the answer is "more than once", you can have multiple forwarding functions that get their function objects out of an array. You will need to preassign slots in this case, because the function in use will indicate which callback to make.

janm
  • 16,989
  • 1
  • 40
  • 60
1

Suppose you create a mapping function:

Foo *inst = // some instance of Foo you're keeping around...

void wrapper(int foo, void *bar){
    inst->handler(foo, bar);
}

Then use wrapper as the callback. Instance semantics in a callback are kind of strange, so I'm not sure how you're going to be sure you bind to the correct instance -- if this is a singleton maybe that doesn't matter.

Mark Elliot
  • 68,728
  • 18
  • 135
  • 157
1

This sentence:

I have a C library function

This means you can NOT pass it any C++ object.
If the library you are using is a C library it does not know about C++ so it can not using anything that is C++ it can only use C stuff.

You MUST make it call a free function in you code.
Now your free function can then call a method on an object (that is why C callbacks have a void* parameter (so you can pass context to the callback)).

Martin York
  • 234,851
  • 74
  • 306
  • 532
  • You're right, but it seems that `set_handler` function doesn't take a `void*` parameter, so I'm wondering how s/he'd set the context. – Xeo Feb 03 '11 at 01:56
1

Here is an ugly hack I invented awhile ago to solve this problem:

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

using ::boost::function;
using ::boost::bind;

typedef int (*callback_t)(const char *, int);

typedef function<int(const char *, int)> MyFTWFunction;

template <MyFTWFunction *callback>
class callback_binder {
 public:
   static int callbackThunk(const char *s, int i) {
      return (*callback)(s, i);
   }
};

extern void register_callback(callback_t f);

int random_func(const char *s, int i)
{
   if (s && *s) {
      return i;
   } else {
      return -1;
   }
}

MyFTWFunction myfunc;

class FooClass {
 public:
   virtual int callme(const char *s, int x) { return 0; };
};

int main(int argc, const char *argv[])
{
   FooClass foo;
   myfunc = bind(&FooClass::callme, &foo, _1, _2);
   register_callback(&callback_binder<&myfunc>::callbackThunk);
   return 0;
}

This could probably be fixed to use stuff from TR1 and remove the dependency on Boost.

And also, of course, myfunc is a global variable. It has to be a global variable. You must have one global variable per different possible object you'd want to call back into. OTOH, you can have as many of these globals as you want.

The main issue here is that it is absolutely impossible to do what you want within the given constraints. The pointer to the object you want to call back into has to come from somewhere. In some languages (like Python for example) you can create a function on-the-fly that has it's own copy of the object pointer. This cannot be done in C++. All functions must exist completely at compile time. You cannot create new function instances at run time.

With C++0x, you can sort of create functions at runtime with lambda functions. But these functions have an unspecified type and there is absolutely no way you could ever then pass them to a C function and have it work. Lambda expressions are meant to be supplied as template parameters and it's pretty hard to use them for anything else because their address can't be taken, and even if it could you ccouldn't actually know what type the pointer is pointing to.

I highly recommend not using it. The little void * most callback interfaces allow you to specify that gets handed back to you along with the data is meant to hold an object pointer of some kind. If possible, you should be doing that instead.

Omnifarious
  • 50,447
  • 15
  • 117
  • 181
  • Mind giving an example where this wraps a class member function? – Xeo Feb 03 '11 at 02:30
  • @Xeo - Oops. *grin* Done. It's fairly trivial to do if you're familiar `::boost::function` and `::boost::bind`, but I should've made sure it was there. – Omnifarious Feb 03 '11 at 02:40
  • +1 for describing at length your "ugly hack" and then recommending not to use it :) –  Feb 03 '11 at 03:08
  • @helloworld - It works. And sometimes it's the only thing you can do because the callback interface was designed by brain-dead monkeys. :-) – Omnifarious Feb 03 '11 at 16:12
0

If you have control over how handler is defined, I recommend using Boost function objects instead of function pointers.

If you HAVE to use function pointers, define handler_t with an extra void* whose value is passed along with the handler, watch out for the gotchas Martin York linked in a comment. Then you have something like this:

typedef void (*handler_t)(int foo, void *bar, void *data);

static handler_t handler_ = NULL;
static void* handler_data_ = NULL;
void set_handler(handler_t h, void *d = NULL) {
    handler_ = h;
    handler_data = d;
}

void handle_event(int foo, void *bar) {
    if (handler_ != NULL) handler_(foo, bar, handler_data_);
}

void foo_handler(int foo, void *bar, void *data) {
    Foo *fooObj = static_cast<Foo*>(data);
    fooObj->handler(foo, bar);
}

// in main
    set_handler(foo_handler, &some_foo_object);
Community
  • 1
  • 1
Walter Mundt
  • 22,993
  • 5
  • 50
  • 60
  • Based on Martin's comment in this post, I wouldn't be able to pass the Boost function object to my C library (or am I missing something?). –  Feb 03 '11 at 02:57
  • You said in a comment, "Yes, I can change the signature of handler_t". If you can do that, which involves changing the code that uses and calls handler_t, why can't you change the callback mechanism to something C++-based as well? – Walter Mundt Feb 03 '11 at 04:04
  • It's not that I *can't*; I simply don't understand how to do what you suggest with the Boost function objects and the C library. The library is 500K+ lines of legacy C code, so changing to C++ is nontrivial. –  Feb 03 '11 at 05:31
  • Ahh, in that case, adding an extra void * to handler_t and defaulting it to NULL is probably your best bet. Just take care with not throwing exceptions over the callback (even if you catch them later, C code is not meant to deal with that and will often leak resources), and with the pointer conversions. – Walter Mundt Feb 03 '11 at 17:48