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

时间:2011-02-03 01:36:41

标签: c++ c

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

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

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

如何在没有静态功能的情况下执行此操作?谢谢!


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

6 个答案:

答案 0 :(得分:8)

至少使用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<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));
}

答案 1 :(得分:3)

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

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

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

答案 2 :(得分:1)

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

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

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

然后使用wrapper作为回调。回调中的实例语义有点奇怪,所以我不确定你是如何确定你绑定到正确的实例 - 如果这是一个单例也许并不重要。

答案 3 :(得分:1)

这句话:

  

我有一个C库函数

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

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

答案 4 :(得分:1)

以下是解决此问题的ugly hack I invented awhile ago

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

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

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

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

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

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

答案 5 :(得分:0)

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

如果您必须使用函数指针,请使用额外的void *定义handler_t,其值与处理程序一起传递,请注意the gotchas Martin York linked in a comment。然后你有这样的事情:

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