简体   繁体   中英

C++ generic callback implementation

I have a code that takes messages from flash player in a form of XML parse them into function and arguments and calls a registered callback for that function. The piece of code that I want to replace is something nicely done (almost) generic Callback mechanism: code for the generic callback implementation of flashSDK (ASInterface.inl) .

The problem with it is that this code is written for flash and I want to replace the flash and use other service that will have the same interface. Is there any standard implementation of this callback mechanism (std? boost? something else open sourced?)?

This code implements generic callbacks mechanism that you can register function with number of arguments and types in a map:

void SomethingHappened(int a, int b) {print a + b;}
void SomethingElseHappened(string abcd) {print abcd;}
callbacks["SomethingHappened"] = &SomethingHappened;
callbacks["SomethingElseHappened"] = &SomethingElseHappened;

and than search for it and call with an array of arguments:

Callbacks::iterator itCallback = callbacks.find(functionName);
if (itCallback != callbacks.end())
{
    HRESULT result = itCallback->second.Call(arguments, returnValue);
}

full usage example:

//init callbacks
std::map<std::wstring, Callback> callbacks;
void SomethingHappened(int a, int b) {print a + b;}
void SomethingElseHappened(string abcd) {print abcd;}
callbacks[functionName] = &SomethingHappened;

void MessageArrived(string xmlInput)
{
    string functionName = parseFunctionName(xmlInput);
    Callbacks::iterator itCallback = callbacks.find(functionName);
    if (itCallback != callbacks.end())
    {
        //parse arguments
        std::vector<std::wstring> args;
        _Args::split(xml, args);
        ASValue::Array arguments;
        for (size_t i = 0, s = args.size(); i < s; ++i)
        {
            ASValue arg; arg.FromXML(args[i]);
            arguments.push_back(arg);
        }
        ASValue returnValue;
        //***this is where the magic happens: call the function***
        HRESULT result = itCallback->second.Call(arguments, returnValue);
        return result;
    }
}

You probably need a wrapper around std::function , something like:

template <typename T> struct Tag{};

// Convert ASValue to expected type,
// Possibly throw for invalid arguments.
bool Convert(Tag<Bool>, AsValue val) { return (Boolean)val; }
int Convert(Tag<int>, AsValue val) { return (Number)val; }
// ...

struct Callback
{
private:
    template <std::size_t ... Is, typename Ret, typename ... Ts>
    static Ret call_impl(Ret(* func)(Ts...), std::index_sequence<Is...>)
    {
        if (arr.size() != sizeof...(Is)) throw std::invalid_argument{};
        return func(Convert(tag<Ts>{}, arr[Is])...);
    }
public:
    template <typename Ret, typename ... Ts>
    Callback(Ret(* func)(Ts...)) : Call{[func](ASValue::Array arr, ASValue& ret)
        {
            try
            {
                ret = Callback::call_impl(func, std::make_index_sequence<sizeof(...(Ts)>());
                return S_OK;
            } catch (...) {
                return E_INVALIDARG;
            }
        }}
    {}

    std::function<HRESULT(ASValue::Array, ASValue&)> Call;
};

std::index_sequence is C++14, but you might find implementation on SO.

You could implement something like that.
A map of objects (GenericCallback here) containing std::function<R(Args...)> objects type-erased with std::any or std::variant .

You need to be careful in the way you call your function callbacks though.

Eg I have to feed it a std::string("hello world") and not a simple C-string, otherwise the std::any_cast will throw (since a function<string(const char*)> is not a function<string(string)> ).

#include <algorithm>
#include <any>
#include <functional>
#include <iostream>
#include <string>
#include <map>
#include <memory>

struct Caller {
    virtual ~Caller() = default;
    virtual std::any call(const std::vector<std::any>& args) = 0;
};


template<typename R, typename... A>
struct Caller_: Caller {

    template <size_t... Is>
    auto make_tuple_impl(const std::vector<std::any>& anyArgs, std::index_sequence<Is...> ) {
        return std::make_tuple(std::any_cast<std::decay_t<decltype(std::get<Is>(args))>>(anyArgs.at(Is))...);
    }

    template <size_t N>
    auto make_tuple(const std::vector<std::any>& anyArgs) {
        return make_tuple_impl(anyArgs, std::make_index_sequence<N>{} );
    }

    std::any call(const std::vector<std::any>& anyArgs) override {
        args = make_tuple<sizeof...(A)>(anyArgs);
        ret = std::apply(func, args);
        return {ret};
    };

    Caller_(std::function<R(A...)>& func_)
    : func(func_)
    {}

    std::function<R(A...)>& func;
    std::tuple<A...> args;
    R ret;
};

struct GenericCallback {

    template <class R, class... A>
    GenericCallback& operator=(std::function<R(A...)>&& func_) {
        func = std::move(func_);
        caller = std::make_unique<Caller_<R, A...>>(std::any_cast<std::function<R(A...)>&>(func));
        return *this;
    }

    template <class Func>
    GenericCallback& operator=(Func&& func_) {
        return *this = std::function(std::forward<Func>(func_));
    }

    std::any callAny(const std::vector<std::any>& args) {
        return caller->call(args);
    }

    template <class R, class... Args>
    R call(Args&&... args) {
        auto& f = std::any_cast<std::function<R(Args...)>&>(func);
        return f(std::forward<Args>(args)...);
    }

    std::any func;
    std::unique_ptr<Caller> caller;
};

using namespace std;

//Global functions
int sub(int a, int b) { return a - b; }
std::function mul = [](int a, int b) { return a*b;};
std::string sortString(std::string str) { 
    std::sort(str.begin(), str.end()); 
    return str;  
}

int main() 
{
    std::map<std::string, GenericCallback> callbacks;

    // Adding our callbacks

    callbacks["add"] = [](int a, int b) { return a + b; };
    callbacks["sub"] = sub;
    callbacks["mul"] = std::move(mul);
    callbacks["sortStr"] = sortString;

    // Calling them (hardcoded params)

    std::cout << callbacks["add"].call<int>(2, 3) << std::endl;
    std::cout << callbacks["sub"].call<int>(4, 2) << std::endl;
    std::cout << callbacks["mul"].call<int>(5, 6) << std::endl;
    std::cout << callbacks["sortStr"].call<std::string>(std::string("hello world")) << std::endl;

    // Calling "add" (vector of any params)

    std::vector<std::any> args = { {1}, {2} };
    std::any result = callbacks["add"].callAny(args);

    std::cout << "result=" << std::any_cast<int>(result) << std::endl;

    return 0;
}

https://godbolt.org/z/h63job

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM