简体   繁体   中英

How to use c++14 variadic templates with sdbus callbacks

The sd-bus requires one to callback functions when defining d-bus methods. As i am doing C++14, i would like to have those calls to a class object on_msg_method_here() functions. What i am trying to achieve is something like this (in pseudo c++):

int callback_dbus_method_foo( message* msg, void* userdata, ... )
{
     MyClass* cls = (MyClass*)userdata;
     Type0 var0;
     if ( message_process( msg, signature[0], &var0 ) != 0 )
        //.. error here
     Type1 var1;
     if ( message_process( msg, signature[1], &var1 ) != 0 )
        //.. error here
     //... and these continue from 0 to N
     TypeN varN;
     if ( message_process( msg, signature[N], &varN ) != 0 )
        //.. error here
     int dbus_ret = cls->on_msg_method_foo( var1, var2, ..., varN )          
     handle_dbus_ret( msg, dbus_ret // ... );
     return 0;
}

int MyClass::register_callbacks( ... )
{

  // Well really we have something really different, this is to demonstrate

  // pass 'this' as userdata* to callback function
  dbus_register_callback( "method_foo", 
         &callback_dbus_method_foo, this )
}

Now i know i can do this with C-macros, but how to do this properly with C++14 varidic macros?

As far as i understand, the trouble of calling certain class object certain method can be handled with std::bind (and pass that via userdata pointer), and the variable declaration and message_process can be done with variadic template peeling, but how to get those declared variables (var0, var1, .. on the pseudo code example) expanded properly to the call? In short, how to do this magic:

MyClass::register_callbacks()
{
   Mystic fun_to_call = std::bind( &MyClass::on_dbus_method_foo, this ); 
   dbus_register_callback( "method_foo", 
            super_magic_template<int,double,bool>, &fun_to_call );
}

There are a couple things I would do in order to get an elegant and generic solution.

We need a way to gather variables (var0, var1, ..., varN) and pass them to a function. For that, I would first have a wrapper that queries such variables given it's index i. I'm not sure what signature is in your exemple, but I'm sure you can work around this.

template <class T>
T get_var(message* msg, unsigned i) {
  T var;
  if ( message_process( msg, signature[i], &var ) != 0)
    throw std::runtime_error("Oups"); // In this context, it's easier to deal with errors with exception.
  return var;
}

We can then gather all variables by unpacking variadic template arguments, along with associated index_sequence used for indexing. Something like

template <class... Vars, class F>
void callback_wrapper(F& fcn, message* msg) {
  callback_wrapper_impl(fcn, msg, std::make_index_sequence<sizeof...(Vars)>());
}
template <class... Vars, class F, size_t... i>
void callback_wrapper_impl(F& fcn, message* msg, std::index_sequence<i...>) {
  fcn(get_var<Vars>(msg, i)...);
}

Another difficulty arises with using std::bind, which returns the function-like object fun_to_call . We can't pass that to dbus_register_callback as a function pointer, which does not carry any data, neither can we pass a pointer to it as userdata, because fun_to_call is a local variable, hence it's lifetime is too short.

Instead of relying only on a super_magic_template callback, I would do a wrapper around dbus_register_callback that offers a simpler interface, let's call it modern_dbus_register_callback . The most straightforward solution I see is to use dynamic storage duration at the cost of memory allocation and an extra level of indirection - this is similar to type erasure used in std::function. Note that you can optimize this if sizeof(fun_to_call) < sizeof(void*) , by passing fun_to_call by value as userdata - this is small value optimization. I believe using lambdas with no capture can be useful, as they are convertibles to function pointers and avoid lots of template boilerplate. Some extra work might be required to handle errors while avoiding memory leaks.

template <class... Vars, class F>
void modern_dbus_register_callback(const char* name, F& fcn) {
  std::unique_ptr<F> fcn_ptr = std::make_unique<F>(fcn);
  dbus_register_callback(name, [](message* msg, void* userdata){
    std::unique_ptr<F> fcn_ptr(static_cast<F*>(userdata));
    callback_wrapper<Vars...>(*fcn_ptr, msg);
  }, fcn_ptr.release());
}

This can then be used as

modern_dbus_register_callback<int,double,bool>("method_foo", fun_to_call);

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