让 C++ 成员函数被 C 回调调用的最佳方法?

Best method to have a C++ member function get called by a C callback ?

提问人:keraba 提问时间:9/26/2008 最后编辑:Coding Mashkeraba 更新时间:9/8/2012 访问量:1893

问:

给定一个典型的类:

struct Whatever
{
    void Doit();
};

Whatever w;

让成员函数被基于 C void* 的回调(如 pthread_create() 或信号处理程序)调用的最佳方法是什么?

pthread_t pid;

pthread_create(&pid, 0, ... &w.Doit() ... );
C++ 函数 结构 回调

评论


答:

1赞 Corey Trager 9/26/2008 #1

成员函数必须是静态的。非静态有一个隐含的“this”参数。将指向 Whatever 实例的指针作为 void* 传递,以便静态成员可以访问该实例。

6赞 Ian G 9/26/2008 #2

大多数 C 回调允许指定参数,例如

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                   void *(*start_routine)(void*), void *arg);

所以你可以有

void myclass_doit(void* x)
{
  MyClass* c = reinterpret_cast<MyClass*>(x);
  c->doit();
}

pthread_create(..., &myclass_doit, (void*)(&obj));

评论

0赞 Greg Rogers 9/26/2008
请注意myclass_doit必须具有 C 链接(即外部“C”)。
0赞 keraba 9/27/2008
不,它没有。它显然是从 C++ 文件中引用的。
0赞 Catskul 9/16/2009
这需要为每个回调使用单独的包装器。诚然,它确实有效,但它造成了巨大的代码管理开销。
0赞 Seb Rose 9/26/2008 #3

请参阅此链接

基本上,这不可能直接实现,因为: “指向非静态成员的指针与普通的 C 函数指针不同,因为它们需要传递类对象的 this-pointer。因此,普通函数指针和非静态成员函数的指针具有不同且不兼容的签名”

3赞 Nils Pipenbrinck 9/26/2008 #4

使用如下所示的 C 函数包装器:

struct Whatever
{
    void Doit();
};

extern "C" static int DoItcallback (void * arg)
{
  Whatever * w = (Whatever *) arg;
  w->DoIt();
  return something;
}

仅当可以以某种方式将指针传递到类时才有效。大多数回调机制都允许这样做。

Afaik,这是执行此操作的唯一方法。如果没有大量的黑客攻击,你就不能直接从 C 中调用方法。

评论

0赞 Catskul 9/16/2009
这需要为每个回调使用单独的包装器。诚然,它确实有效,但它造成了巨大的代码管理开销。
0赞 bmdhacks 9/26/2008 #5

虽然我没有在 C 中使用它来做回调,但我强烈建议你看看 libsigc++。这正是我在执行 C++ 回调时多次需要的。

评论

0赞 keraba 9/27/2008
libsigc++ 是否定义了函数对象(无法从 C 调用)?只是检查。否则,是的,我同意它是有用的。
3赞 hazzen 9/26/2008 #6

成员函数是私有的吗?如果没有,请使用标准成语:

void* pthread_foo_caller(void* arg) {
    Foo* foo = static_cast<Foo*>(arg);
    foo->bar();
    return NULL;
}

如果成员函数是私有的,则可以在类中声明一个静态方法,该方法采用“this”指针并调用相应的方法。例如:

class Foo {
 public:
  static pthread_foo_caller(void* arg);
  ...
};

void* Foo::pthread_foo_caller(void* arg) {
    Foo* foo = static_cast<Foo*>(arg);
    foo->private_bar();
    return NULL;
}
1赞 Torbjörn Gyllebring 9/27/2008 #7

这里有一个简单的方法,不要忘记正确管理“MemberFunction”对象的生存期。

#include 

class MyClass
{
public:
    void DoStuff()
    {
        printf("Doing Stuff!");
    }
};

struct MemberFunction
{
    virtual ~MemberFunction(){}
    virtual void Invoke() = 0;
};

void InvokeMember(void *ptr)
{
    static_cast(ptr)->Invoke();
}

template 
struct MemberFunctionOnT : MemberFunction
{
    typedef void (T::*function_t)();
public:
    MemberFunctionOnT(T* obj, function_t fun)
    {
        m_obj = obj;
        m_fun = fun;
    }

    void Invoke()
    {
        (m_obj->*m_fun)();
    }
private:
    T *m_obj;
    function_t m_fun;
};

template 

MemberFunction* NewMemberFunction(T *obj, void (T::*fun)())
{ 
    return new MemberFunctionOnT(obj, fun); 
}

//simulate a C-style function offering callback functionality.
void i_will_call_you_later(void (*fun)(void*), void *arg)
{
    fun(arg);
}

int main()
{
    //Sample usage.
    MyClass foo;

    MemberFunction *arg = NewMemberFunction(&foo, &MyClass::DoStuff);
    i_will_call_you_later(&InvokeMember, arg);
    return 0;
}
6赞 keraba 9/27/2008 #8

最简洁的解决方案是,在所有代码共享的头文件中定义:

template <typename T, void (T::*M)()>
void* thunk(
    void* p)
{
    T* pt = static_cast<T*>(p);

    (pt->*M)();

    return 0;
}

您可能希望定义 4 个版本:一个是 thunk 返回 void 和 void 的版本,另一个是成员函数返回 void 和 void 的版本。这样,编译器就可以根据具体情况匹配最好的编译器(事实上,如果一切都不匹配,它会抱怨。**

然后,每次遇到以下情况之一时,您只需输入以下代码即可:

pthread_create(&pid, 0, &thunk<随便, &随便::d oit>, &w);

当方法是私有的时,只要从类的代码中引用该方法,这甚至会起作用。(如果没有,我不得不想知道为什么代码引用了私有方法。

1赞 plinth 9/27/2008 #9

你应该注意的一件事是,如果你编写这样的代码:

try {
    CallIntoCFunctionThatCallsMeBack((void *)this, fCallTheDoItFunction);
} catch (MyException &err)
{
   stderr << "badness.";
}

void fCallTheDoItFunction(void *cookie)
{
    MyClass* c = reinterpret_cast<MyClass*>(cookie);
    if (c->IsInvalid())
        throw MyException;
    c->DoIt();
}

您可能会遇到一些严重的麻烦,具体取决于您的编译器。事实证明,在一些编译器中,在优化时,他们在 try/catch 块中看到单个 C 调用,并高兴地喊道:“我正在调用一个 C 函数,因为它是老式的 C,所以不能抛出!老茧!我将删除 try/catch 的所有残留物,因为它永远不会到达。

愚蠢的编译器。

不要打电话给 C 回电并期望能够接听。

评论

0赞 C. K. Young 9/27/2008
好吧,如果你的代码是由 C(或其他语言)代码调用的,你需要确保没有异常“逃逸”回 C/other,所以这个建议是同义的。9-3