如何使用带有 sdbus 回调的 c++14 可变参数模板
How to use c++14 variadic templates with sdbus callbacks
sd-bus 在定义 d-bus 方法时需要一个回调函数。当我在做 C++14 时,我想调用 class 对象 on_msg_method_here() 函数。我想要实现的是这样的(在伪 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 )
}
现在我知道我可以使用 C 宏来做到这一点,但是如何正确地 使用 C++14 varidic 宏来做到这一点?
据我了解,调用某些 class 对象某些方法的麻烦可以用 std::bind 处理(并通过用户数据指针传递),以及变量声明和 message_process 可以通过可变参数模板剥离来完成,但是如何将那些声明的变量(伪代码示例中的 var0、var1、..)正确扩展到调用中?简而言之,如何做到这一点:
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 );
}
为了获得优雅且通用的解决方案,我会做几件事。
我们需要一种方法来收集变量(var0、var1、...、varN)并将它们传递给函数。为此,我首先要有一个包装器,它在给定索引 i 的情况下查询此类变量。我不确定你的例子中的 signature
是什么,但我相信你可以解决这个问题。
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;
}
然后我们可以通过解压可变参数模板参数以及用于索引的关联 index_sequence 来收集所有变量。像
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)...);
}
另一个困难出现在使用 std::bind 时,returns 类函数对象 fun_to_call
。我们不能将它作为函数指针传递给 dbus_register_callback
,它不携带任何数据,我们也不能将指针作为用户数据传递给它,因为 fun_to_call
是一个局部变量,因此它的生命周期是太短了。
我不会只依赖 super_magic_template
回调,而是围绕 dbus_register_callback
做一个包装器,它提供了一个更简单的界面,我们称之为 modern_dbus_register_callback
。我看到的最直接的解决方案是以内存分配和额外的间接级别为代价使用动态存储持续时间——这类似于 std::function 中使用的类型擦除。请注意,如果 sizeof(fun_to_call) < sizeof(void*)
,您可以通过按值将 fun_to_call
作为用户数据传递来优化它 - 这是小值优化。我相信使用不带捕获的 lambda 会很有用,因为它们可以转换为函数指针并避免大量模板样板。可能需要一些额外的工作来处理错误,同时避免内存泄漏。
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());
}
这可以用作
modern_dbus_register_callback<int,double,bool>("method_foo", fun_to_call);
sd-bus 在定义 d-bus 方法时需要一个回调函数。当我在做 C++14 时,我想调用 class 对象 on_msg_method_here() 函数。我想要实现的是这样的(在伪 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 )
}
现在我知道我可以使用 C 宏来做到这一点,但是如何正确地 使用 C++14 varidic 宏来做到这一点?
据我了解,调用某些 class 对象某些方法的麻烦可以用 std::bind 处理(并通过用户数据指针传递),以及变量声明和 message_process 可以通过可变参数模板剥离来完成,但是如何将那些声明的变量(伪代码示例中的 var0、var1、..)正确扩展到调用中?简而言之,如何做到这一点:
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 );
}
为了获得优雅且通用的解决方案,我会做几件事。
我们需要一种方法来收集变量(var0、var1、...、varN)并将它们传递给函数。为此,我首先要有一个包装器,它在给定索引 i 的情况下查询此类变量。我不确定你的例子中的 signature
是什么,但我相信你可以解决这个问题。
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;
}
然后我们可以通过解压可变参数模板参数以及用于索引的关联 index_sequence 来收集所有变量。像
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)...);
}
另一个困难出现在使用 std::bind 时,returns 类函数对象 fun_to_call
。我们不能将它作为函数指针传递给 dbus_register_callback
,它不携带任何数据,我们也不能将指针作为用户数据传递给它,因为 fun_to_call
是一个局部变量,因此它的生命周期是太短了。
我不会只依赖 super_magic_template
回调,而是围绕 dbus_register_callback
做一个包装器,它提供了一个更简单的界面,我们称之为 modern_dbus_register_callback
。我看到的最直接的解决方案是以内存分配和额外的间接级别为代价使用动态存储持续时间——这类似于 std::function 中使用的类型擦除。请注意,如果 sizeof(fun_to_call) < sizeof(void*)
,您可以通过按值将 fun_to_call
作为用户数据传递来优化它 - 这是小值优化。我相信使用不带捕获的 lambda 会很有用,因为它们可以转换为函数指针并避免大量模板样板。可能需要一些额外的工作来处理错误,同时避免内存泄漏。
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());
}
这可以用作
modern_dbus_register_callback<int,double,bool>("method_foo", fun_to_call);