我尝试过各种各样的设计方法来解决这个问题,但我似乎无法做到这一点。
我需要公开一些静态函数来用作C lib的回调函数。但是,我希望实际的实现是非静态的,所以我可以使用虚函数并在基类中重用代码。如:
class Callbacks {
static void MyCallBack() { impl->MyCallBackImpl(); }
...
class CallbackImplBase {
virtual void MyCallBackImpl() = 0;
但是我尝试解决这个问题(Singleton,让回调包含在实现者类中,等等)我最终陷入了死胡同(impl通常最终指向基类,而不是派生类)。
我想知道它是否完全可能,或者我是否坚持创建某种辅助函数而不是使用继承?
答案 0 :(得分:8)
虽然它看起来似乎对您的设置起作用,但由于未定义C ++ ABI,因此无法保证其正常工作。因此从技术上讲,您不能将C ++静态成员函数用作C代码使用的函数指针。
所有C callacks(我知道)允许您将用户数据作为void *传回。您可以将其用作指向具有虚方法的对象的指针。 但是您必须确保在将基础类(具有回调中使用的虚方法的基类)用于转换为void *之前使用dynamic_cast<>(),否则指针位于另一个end可能无法正确解释(特别是如果涉及多个继承)。
异常:C不适用于异常(特别是带有回调的旧C库)。所以不要指望逃避回调的异常为调用者提供任何有意义的东西(它们更有可能导致应用程序终止)。
你需要做的是使用extern“C”函数作为回调,它在知道类型的对象上调用虚方法并抛弃所有异常。
C pthread例程的示例
#include <iostream>
extern "C" void* start_thread(void* data);
class Work
{
public:
virtual ~Work() {}
virtual void doWork() = 0;
};
/*
* To be used as a callback for C code this MUST be declared as
* with extern "C" linkage to make sure the calling code can
* correctly call it
*/
void* start_thread(void* data)
{
/*
* Use reinterpret_cast<>() because the only thing you know
* that you can do is cast back to a Work* pointer.
*
*/
Work* work = reinterpret_cast<Work*>(data);
try
{
work->doWork();
}
catch(...)
{
// Never let an exception escape a callback.
// As you are being called back from C code this would probably result
// in program termination as the C ABI does not know how to cope with
// exceptions and thus would not be able to unwind the call stack.
//
// An exception is if the C code had been built with a C++ compiler
// But if like pthread this is an existing C lib you are unlikely to get
// the results you expect.
}
return NULL;
}
class PrintWork: public Work
{
public:
virtual void doWork()
{
std::cout << "Hi \n";
}
};
int main()
{
pthread_t thread;
PrintWork printer;
/*
* Use dynamic_cast<>() here because you must make sure that
* the underlying routine receives a Work* pointer
*
* As it is working with a void* there is no way for the compiler
* to do this intrinsically so you must do it manually at this end
*/
int check = pthread_create(&thread,NULL,start_thread,dynamic_cast<Work*>(&printer));
if (check == 0)
{
void* result;
pthread_join(thread,&result);
}
}
答案 1 :(得分:2)
这是可能的。也许在初始化具体实现方面存在问题?
事实上,我记得有一个库与它非常相似。您可能会发现查看libxml++源代码很有用。它建立在libxml之上,这是一个C库。
libxml ++使用静态函数结构来处理回调。对于定制,该设计允许用户(通过虚拟功能)提供他/她自己的实现,然后转发回调。我想这几乎就是你的情况。
答案 2 :(得分:1)
如下所示。单例在Callback类中,Instance成员将返回一个静态分配的对CallbackImpl类的引用。这是一个单例,因为只有在首次调用函数时才会初始化引用。此外,它必须是引用或指针,否则虚函数将无法工作。
class CallbackImplBase
{
public:
virtual void MyCallBackImpl() = 0;
};
class CallbackImpl : public CallbackImplBase
{
public:
void MyCallBackImpl()
{
std::cout << "MyCallBackImpl" << std::endl;
}
};
class Callback
{
public:
static CallbackImplBase & Instance()
{
static CallbackImpl instance;
return instance;
}
static void MyCallBack()
{
Instance().MyCallBackImpl();
}
};
extern "C" void MyCallBack()
{
Callback::MyCallBack();
}
答案 3 :(得分:0)
是否已为用户定义了传递给回调函数的任何参数?有没有办法可以将用户定义的值附加到传递给这些回调的数据?我记得当我为Win32窗口实现一个包装器库时,我使用SetWindowLong()将一个this
指针附加到窗口句柄,稍后可以在回调函数中检索它。基本上,您需要将this
指针打包到某处,以便在回调被触发时可以检索它。
struct CALLBACKDATA
{
int field0;
int field1;
int field2;
};
struct MYCALLBACKDATA : public CALLBACKDATA
{
Callback* ptr;
};
registerCallback( Callback::StaticCallbackFunc, &myCallbackData, ... );
void Callback::StaticCallbackFunc( CALLBACKDATA* pData )
{
MYCALLBACKDATA* pMyData = (MYCALLBACKDATA*)pData;
Callback* pCallback = pMyData->ptr;
pCallback->virtualFunctionCall();
}