C++: static function wrapper that routes to member function?

前端 未结 4 335
北荒
北荒 2021-01-14 05:45

I\'ve tried all sorts of design approaches to solve this problem, but I just can\'t seem to get it right.

I need to expose some static functions to use as callback f

相关标签:
4条回答
  • 2021-01-14 06:28

    Something like the below. The singleton is in class Callback, the Instance member will return a statically allocated reference to a CallbackImpl class. This is a singleton because the reference will only be initialised once when the function is first called. Also, it must be a reference or a pointer otherwise the virtual function will not work.

    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();
    }
    
    0 讨论(0)
  • 2021-01-14 06:30

    Problem 1:

    Though it may look and seem to work on your setup this is not guaranteed to work as the C++ ABI is not defined. So technically you can not use C++ static member functions as functions pointers to be used by C code.

    Problem 2:

    All C callacks (that I know of) allow you to pass user data back as a void*. You can use this as the pointer to your object that has the virtual method. BUT You must make sure you use dynamic_cast<>() to the base class (the one with the virtual method used in the callback) before it is converted into the void* otherwise the pointer at the other end may not be interpreted correctly (especially if there is multiple inheritance involved).

    Problem 3:

    Exceptions: C is not designed to work with exceptions (especially old C libraries with callbacks). So don't expect exceptions that escape your callback to provide anything meaningful to the caller (they are more likely to result in application termination).

    Solution:

    What you need to do is use extern "C" function as the callback that calls the virtual method on an object of know type and throws away all exceptions.

    An example for the C pthread routines

    #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);
        }
    }
    
    
        
    
    0 讨论(0)
  • 2021-01-14 06:33

    Are any of the parameters passed to the callback function user defined? Is there any way you can attach a user defined value to data passed to these callbacks? I remember when I implemented a wrapper library for Win32 windows I used SetWindowLong() to attach a this pointer to the window handle which could be later retrieved in the callback function. Basically, you need to pack the this pointer somewhere so that you can retrieve it when the callback gets fired.

    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();
    }
    
    0 讨论(0)
  • 2021-01-14 06:44

    It's possible. Perhaps there's a problem on how you're initializing the concrete implementation?

    In fact, I remember one library that does something very similar to this. You might find it usefull to take a look at libxml++ source code. It's built on top of libxml, which is a C library.

    libxml++ uses a struct of static functions to handle the callbacks. For customization, the design allows the user to provide (through virtual functions) his/her own implementations to which the callbacks are then forwarded. I guess this is pretty much your situation.

    0 讨论(0)
提交回复
热议问题