通过C回调调用C++成员函数的最佳方法是什么?
给定一个典型的类:通过C回调调用C++成员函数的最佳方法是什么?
struct Whatever { void Doit(); }; Whatever w;
什么是让成员函数的最好办法通过C void *的基于回调,如在pthread_create()或信号处理函数中调用?
pthread_t pid; pthread_create(&pid, 0, ... &w.Doit() ...);
成员函数必须是静态的。非静态有一个暗示的“这个”论点。将指针传递给你的任何实例作为void *,以便静态成员可以获取实例。
大多数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));
看到这个link
基本上,它不是直接的,因为: “指针到非静态成员是因为普通的C函数指针不同他们需要传递一个类对象的this指针,因此普通函数指针和指向非静态成员函数的指针具有不同的和不兼容的签名“
使用C函数的包装是这样的:
struct Whatever
{
void Doit();
};
extern "C" static int DoItcallback (void * arg)
{
Whatever * w = (Whatever *) arg;
w->DoIt();
return something;
}
只有工作,如果你可以将指针传递到类莫名其妙。大多数回调机制都允许这样做。
Afaik这是执行此操作的唯一方法。没有大量黑客攻击,你不能直接从C中调用方法。
这需要为每个回调单独封装。它确实有效,但会造成大量的代码管理开销。 – Catskul 2009-09-15 16:41:25
成员函数是否为私有?如果没有,使用标准的成语:
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;
}
这里有一个简单的方法来做到这一点,不要忘了妥善管理你的“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;
}
最简洁的解决方案是定义,由所有的代码共享的头文件:
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 *
。这样编译器可以匹配最好的一个,这取决于具体情况(事实上,如果一切不匹配,它都会抱怨。)
然后你必须输入你每次碰到这些情形之一的时间:
在pthread_create(& PID,0,&咚<不管结果如何,&无论:: DOIT>,& W);
只要方法是从类的代码中引用的,这个方法甚至可以用于私有方法。 (如果没有,我想知道为什么代码被引用的私有方法),你应该知道的
的一件事是,如果你写这样的代码:
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调用你回来,希望能够赶上。
只是注意myclass_doit必须有c链接(即extern“C”) – 2008-09-26 15:37:56
不,它显然是从C++文件引用的 – keraba 2008-09-26 18:08:47
这需要为每个回调分别包装。它创造了一个拥抱e乱码管理开销。 – Catskul 2009-09-15 16:42:58