使用C ++类成员函数(不能是静态的)作为C回调函数

我有一个C库函数,需要一个函数指针进行回调,我想传入一个C ++成员函数。 C ++函数修改了一个成员变量,所以我不能使用静态自由函数(如几个类似post中所建议的那样)。 我的尝试(如下所示)因编译器错误而失败。

这篇文章最接近我的要求:

使用C ++类成员函数作为C回调函数

如果没有静态函数,我怎么能这样做? 谢谢!


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  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  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*))' 

至少使用handler_t签名是不可能的。

虽然您可以在.cpp上创建一个自由函数来包装成员调用,但您需要一个指向Foo实例的指针:

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

您需要一些void *来将Foo实例作为处理程序属性传递:

 // 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(arg1); some_foo_instance->handler(foo, bar); } // main int main(int argc, char **argv) { Foo some_concrete_instance; set_handler(&my_wrap, static_cast(&some_concrete_instance)); } 

最大的问题是需要多次调用set_handler来调用不同对象上的方法。 如果这个答案是一个,你可以做这样的事情:

 #include  class HandlerContext { static boost::function s_func static void forward(int foo, void* bar) { s_func(foo, bar); } public: static void set(boost::function const& f) { s_func = f; set_handler(&HandlerContext::forward); } }; 

如果答案是“不止一次”,则可以使用多个转发函数将其函数对象从数组中取出。 在这种情况下,您需要预先分配插槽,因为正在使用的function将指示要进行的回调。

这个句子:

我有一个C库函数

这意味着你不能传递任何C ++对象。
如果你使用的库是一个C库,它不知道C ++,所以它不能使用任何C ++它只能使用C的东西。

必须让它在你的代码中调用一个自由函数。
现在,您的自由函数可以调用对象上的方法(这就是C回调具有void *参数的原因(因此您可以将上下文传递给回调))。

假设您创建了一个映射函数:

 Foo *inst = // some instance of Foo you're keeping around... void wrapper(int foo, void *bar){ inst->handler(foo, bar); } 

然后使用wrapper作为回调。 回调中的实例语义有点奇怪,所以我不确定你将如何确定你绑定到正确的实例 – 如果这是一个单例可能无关紧要。

这是我前一段时间发明的一个丑陋的黑客来解决这个问题:

 #include  #include  using ::boost::function; using ::boost::bind; typedef int (*callback_t)(const char *, int); typedef function MyFTWFunction; template  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; } 

这可能是固定使用来自TR1的东西并删除对Boost的依赖。

当然, myfunc也是一个全局变量。 它必须是一个全局变量。 每个不同的可能对象必须有一个全局变量,您想要回调。 OTOH,你可以拥有任意数量的全局变量。

这里的主要问题是在给定的约束条件下完全不可能做到你想要的。 指向要回调的对象的指针必须来自某个地方。 在某些语言中(例如Python),您可以在运行中创建一个具有对象指针副本的函数。 这不能在C ++中完成。 所有函数必须在编译时完全存在。 您无法在运行时创建新的函数实例。

使用C ++ 0x,您可以使用lambda函数在运行时创建函数。 但是这些函数有一个未指定的类型,你绝对没有办法将它们传递给C函数并让它工作。 Lambda表达式是作为模板参数提供的,并且很难将它们用于其他任何东西,因为它们的地址不能被采用,即使它实际上你也不知道指针指向的是什么类型。

我强烈建议不要使用它。 小void *大多数回调接口允许您指定与数据一起交还给您的数据意味着保存某种对象指针。 如果可能的话,你应该这样做。

如果您可以控制如何定义处理程序,我建议使用Boost函数对象而不是函数指针。

如果您必须使用函数指针,请使用额外的void *定义handler_t,其值与处理程序一起传递,请注意Martin York在注释中链接的陷阱 。 然后你有这样的事情:

 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(data); fooObj->handler(foo, bar); } // in main set_handler(foo_handler, &some_foo_object);