lundi 1 février 2021

C++ thunk class implementation for forwarding C function callbacks to C++ member functions

I attempted implementing a "thunk" class in C++ that allows obtaining a C function pointer to pass to C code that eventually calls a C++ member function. A specific requirement was that it works with C code that does not pass a void * context parameter (or similar) as is frequent in C code callbacks. Hence, a distinct global or static C glue function has to be created for each C<->C++ link. In my implementation this is realized through the template parameter N, which has to be distinct for each use of the thunk class and causes a different "trampoline" function to be created for each template concretization.

My question is: is there a way to implement this without the need for the template parameter N or a way for the compiler to automatically assign a new value N to each concretization based on some template "magic"?

#ifndef THUNK_H
#define THUNK_H

#include <map>
#include <stdexcept>

template <class Sig, class Obj, size_t N>
class Thunk;

class ThunkBase
{
public:
    static void clear()
    {
        for (auto it : mThunkStorage) {
            delete it.second;
        }

        mThunkStorage.clear();
    }

    virtual ~ThunkBase() {}

protected:
    static std::map<void *, ThunkBase *> mThunkStorage;
};

template <class R, class... Args, class Obj, size_t N>
class Thunk<R(*)(Args...), Obj, N> : public ThunkBase
{
public:
    Thunk(Obj &obj, R(Obj::*method)(Args...)) : mObj(obj), mMethod(method)
    {
        std::pair<decltype(mThunkStorage)::iterator, bool> resPair = mThunkStorage.emplace(
                (void *)(&Thunk<R(*)(Args...), Obj, N>::trampoline),
                this);

        if (!resPair.second) {
            throw std::runtime_error("failed to insert thunk object");
        }
    }

    R(*entry())(Args...)
    {
        return &Thunk<R(*)(Args...), Obj, N>::trampoline;
    }

private:
    Obj &mObj;
    R(Obj::*mMethod)(Args...);

    static R trampoline(Args... args)
    {
        auto selfFuncPtr = &Thunk<R(*)(Args...), Obj, N>::trampoline;
    
        auto it = mThunkStorage.find((void *)selfFuncPtr);
        if (it == mThunkStorage.end()) {
            throw std::runtime_error("could not find thunk object");
        }

        ThunkBase *base = it->second;
        Thunk<R(*)(Args...), Obj, N> *self = static_cast<Thunk<R(*)(Args...), Obj, N> *>(base);

        Obj &obj = self->mObj;
        R(Obj::*method)(Args...) = self->mMethod;
        return (obj.*method)(args...);
    }
};

#endif

std::map<void *, ThunkBase *> ThunkBase::mThunkStorage;

// -> usage would be like, for example

struct MyClass
{
    void myFunc(int);
};

MyClass myObj;
Thunk<void(*)(int), MyClass, 0> myThunk(myObj, &MyClass::myFunc);
void (*myFunc)(int) = myThunk.entry();

Aucun commentaire:

Enregistrer un commentaire