C ++:路由到成员函数的静态函数包装器?

时间:2009-11-10 12:22:31

标签: c++ design-patterns callback

我尝试过各种各样的设计方法来解决这个问题,但我似乎无法做到这一点。

我需要公开一些静态函数来用作C lib的回调函数。但是,我希望实际的实现是非静态的,所以我可以使用虚函数并在基类中重用代码。如:

class Callbacks {
  static void MyCallBack() { impl->MyCallBackImpl(); }
  ...

class CallbackImplBase {
   virtual void MyCallBackImpl() = 0;

但是我尝试解决这个问题(Singleton,让回调包含在实现者类中,等等)我最终陷入了死胡同(impl通常最终指向基类,而不是派生类)。

我想知道它是否完全可能,或者我是否坚持创建某种辅助函数而不是使用继承?

4 个答案:

答案 0 :(得分:8)

问题1:

虽然它看起来似乎对您的设置起作用,但由于未定义C ++ ABI,因此无法保证其正常工作。因此从技术上讲,您不能将C ++静态成员函数用作C代码使用的函数指针。

问题2:

所有C callacks(我知道)允许您将用户数据作为void *传回。您可以将其用作指向具有虚方法的对象的指针。 但是您必须确保在将基础类(具有回调中使用的虚方法的基类)用于转换为void *之前使用dynamic_cast<>(),否则指针位于另一个end可能无法正确解释(特别是如果涉及多个继承)。

问题3:

异常: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();
}