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

Posted

技术标签:

【中文标题】让 C 回调调用 C++ 成员函数的最佳方法?【英文标题】:Best method to have a C++ member function get called by a C callback ? 【发布时间】:2010-09-13 12:04:08 【问题描述】:

给定一个典型的类:

结构随便 无效 Doit(); ; 不管w;

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

pthread_t pid; pthread_create(&pid, 0, ... &w.Doit() ... );

【问题讨论】:

【参考方案1】:

大多数 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));

【讨论】:

请注意 myclass_doit 必须有 c 链接(即 extern "C")。 不,它没有。它显然是从 C++ 文件中引用的。 这需要为每个回调单独包装。确实可以,但会造成大量代码管理开销。【参考方案2】:

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

模板 无效*重击( 无效* p) T* pt = static_cast(p); (pt->*M)(); 返回0;

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

那么,每次遇到这些情况之一时,您只需输入:

pthread_create(&pid, 0, &thunk, &w);

只要方法是从类的代码中引用的,这甚至可以在方法是私有的时起作用。 (如果不是,我想知道为什么代码引用了私有方法。)

【讨论】:

【参考方案3】:

像这样使用 C 函数包装器:

struct Whatever

    void Doit();
;

extern "C" static int DoItcallback (void * arg)

  Whatever * w = (Whatever *) arg;
  w->DoIt();
  return something;

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

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

【讨论】:

这需要为每个回调单独包装。确实可以,但它会造成大量代码管理开销。【参考方案4】:

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

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;

【讨论】:

【参考方案5】:

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

【讨论】:

【参考方案6】:

这是一个简单的方法,不要忘记正确管理“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;

【讨论】:

【参考方案7】:

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

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,所以不能抛出!Calloo-cally ! 我将删除所有 try/catch 的痕迹,因为它永远无法到达。

愚蠢的编译器。

不要调用 C 来回叫你并期望能够赶上。

【讨论】:

好吧,如果你的代码是由 C(或其他语言)代码调用的,你需要确保没有异常“逃逸”回 C/other,所以这个建议是重言式的。 :-P【参考方案8】:

看到这个link

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

【讨论】:

【参考方案9】:

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

【讨论】:

libsigc++ 不定义函数对象(不能从 C 调用)?只是检查。否则,是的,我同意它很有用。

以上是关于让 C 回调调用 C++ 成员函数的最佳方法?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 C++ 成员函数作为 C 框架的回调函数

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

C++中类成员函数作为回调函数

如何实现类的成员函数作为回调函数

如何使用 C++ lambda 将成员函数指针转换为普通函数指针以用作回调

C++ 成员函数作为外部库的回调函数