vendredi 20 juillet 2018

Creating a template to wrap C++ member functions and expose as C callbacks

When a C++ application uses a C library that has callbacks in the API, a common pattern is for the app to define static functions that translate some void* user data argument into a class pointer and then call the appropriate member function. The duplication wastes time both writing and reading. It would be nice to have a template function to do this wrapping for me.

I've got part of the way there already...

// C library with some callbacks

typedef void (*CallbackTypeOne)(void* userdata, double arg1);
typedef void (*CallbackTypeTwo)(void* userdata, int arg1);
typedef void (*CallbackTypeThree)(void* userdata, int arg1, float arg2);

typedef void(*GenericCallback)();

void registerAndCallCallback(int typeID, GenericCallback callback, void* userdata)
{
    switch (typeID) {
    case 0: ((CallbackTypeOne)callback)(userdata, 42.0); break;
    case 1: ((CallbackTypeTwo)callback)(userdata, 42); break;
    case 2: ((CallbackTypeThree)callback)(userdata, 42, 42.0f); break;
    };
}

// C++ app using the above library

class MyClass
{
    public:
    MyClass()
    {
        // Ideal short syntax, but doesn't compile
        registerAndCallCallback(0,
            reinterpret_cast<GenericCallback>(
                &staticCallback<MyClass::callbakcOne>),
            this);
        // main.cpp:26:36: error: reinterpret_cast cannot resolve overloaded function 'staticCallback' to type 'GenericCallback' (aka 'void (*)()')
        registerAndCallCallback(1, reinterpret_cast<GenericCallback>(&staticCallback<MyClass::callbakcTwo>), this);
        registerAndCallCallback(2, reinterpret_cast<GenericCallback>(&staticCallback<MyClass::callbakcThree>), this);

        // This works, but I feel there should be a nicer way that avoids having to pass the callback arguments. Avoiding the duplication in decltype would be nice too.
        registerAndCallCallback(0,
            reinterpret_cast<GenericCallback>(
                &staticCallback<decltype(&MyClass::callbakcOne),
                                &MyClass::callbakcOne, double>),
            this);
        registerAndCallCallback(1, reinterpret_cast<GenericCallback>(&staticCallback<decltype(&MyClass::callbakcTwo), &MyClass::callbakcTwo, int>), this);
        registerAndCallCallback(2, reinterpret_cast<GenericCallback>(&staticCallback<decltype(&MyClass::callbakcThree), &MyClass::callbakcThree, int, float>), this);
    }

    void callbakcOne(double arg1) {}
    void callbakcTwo(int arg1) {}
    void callbakcThree(int arg1, float arg2) {}

    template<typename MemberCB, MemberCB cb, typename... Args>
    static void staticCallback(void* userdata, Args... args)
    {
        auto instance = reinterpret_cast<MyClass*>(userdata);
        (instance->*cb)(args...);
    }
};

int main()
{
    MyClass myclass;
    return 0;
}

Is there some way I can change the staticCallback template to automatically resolve the arguments of MyClass::callback*?

Aucun commentaire:

Enregistrer un commentaire