views:

361

answers:

4

Hi, 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 function to a C lib. However, I want the actual implementation to be non-static, so I can use virtual functions and reuse code in a base class. Such as:

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

class CallbackImplBase {
   virtual void MyCallBackImpl() = 0;

However I try to solve this (Singleton, composition by letting Callbacks be contained in the implementor class, etc) I end up in a dead-end (impl usually ends up pointing to the base class, not the derived one).

I wonder if it is at all possible or if I'm stuck with creating some sort of helper functions instead of using inheritance?

+1  A: 

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();
}
Evän Vrooksövich
There is no dedicated parameter for user data. There is one struct I can pass in, but I don't know if it is used somehow in the lib and therefore I would prefer it remaining a PODS as today (which it won't be if I use virtual functions etc).
Frank
If there is a struct you pass in (assuming you're passing a pointer to the struct) you can derive from this struct and add a new `Callbacks*` field. The lib will be none the wiser that some extra data is tacked onto the end of the struct.
Evän Vrooksövich
Thanks, I will try it, sry for not being able to upvote
Frank
You can't use a static member function as a C function pointer for callback. The C++ ABI is no defined and though this may work on your compiler it is not guaranteed by the compiler.
Martin York
If you're worried, specify an explicit calling convention for the static member callback.
Evän Vrooksövich
@Evan: There is no way to do that in standard C++. Some compilers have specific compiler specific tricks but again this is non portable.
Martin York
+2  A: 

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.

ltcmelo
+1  A: 

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();
}
avid
Ahh, but I did use an extern "C" function as the actual callback, I merely used the class to hold the singleton.
avid
+7  A: 

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);
    }
}
Martin York
Why do you use reinterpret_cast instead of static_cast to get the Work* out of the void*?
Bill
I find that this is the exact situation that reinterpret_cast<> is designed for (casting pointers to and from void* (static cast is designed to cast to/from types that are similar)). It is also a type of documentation:- __Look__ a reinterpret_cast<> be very careful when you modify this code.
Martin York