sdbus-c++ Conveniece API
便捷 API 完全抽象了底层 D-Bus 消息和 D-Bus 数据类型,在编译时推断和解析了很多东西,与基本层相比,运行时性能成本接近于零。因此,使用便捷 API 编写的代码更具表现力、在更高的抽象层次上(更接近所要解决问题的抽象层次)、更短更简洁、几乎与使用基本 API 层编写的一样快。
使用此层编写的代码以声明的方式表达了它做什么,而不是如何做。
1. 连接器的实现示例
1.1 同步Server端
#include <sdbus-c++/sdbus-c++.h>
#include <vector>
#include <string>
int main(int argc, char *argv[])
{
// Create D-Bus connection to the (either system or session) bus and requests a well-known name on it.
sdbus::ServiceName serviceName{"org.sdbuscpp.concatenator"};
auto connection = sdbus::createBusConnection(serviceName);
// Create concatenator D-Bus object.
sdbus::ObjectPath objectPath{"/org/sdbuscpp/concatenator"};
auto concatenator = sdbus::createObject(*connection, std::move(objectPath));
auto concatenate = [&concatenator](const std::vector<int> numbers, const std::string& separator)
{
// Return error if there are no numbers in the collection
if (numbers.empty())
throw sdbus::Error(sdbus::Error::Name{"org.sdbuscpp.Concatenator.Error"}, "No numbers provided");
std::string result;
for (auto number : numbers)
{
result += (result.empty() ? std::string() : separator) + std::to_string(number);
}
// Emit 'concatenated' signal
concatenator->emitSignal("concatenated").onInterface("org.sdbuscpp.Concatenator").withArguments(result);
return result;
};
// Register D-Bus methods and signals on the concatenator object, and exports the object.
concatenator->addVTable(sdbus::registerMethod("concatenate").implementedAs(std::move(concatenate)),
sdbus::registerSignal("concatenated").withParameters<std::string>())
.forInterface("org.sdbuscpp.Concatenator");
// Run the loop on the connection.
connection->enterEventLoop();
}
提示:
addVTable(...).forInterface()
方法还有一个带有参数return_slot_t
的重载,返回一个插槽对象。
1.2 同步Client端
#include <sdbus-c++/sdbus-c++.h>
#include <vector>
#include <string>
#include <iostream>
#include <unistd.h>
void onConcatenated(const std::string& concatenatedString)
{
std::cout << "Received signal with concatenated string " << concatenatedString << std::endl;
}
int main(int argc, char *argv[])
{
// Create proxy object for the concatenator object on the server side
sdbus::ServiceName destination{"org.sdbuscpp.concatenator"};
sdbus::ObjectPath objectPath{"/org/sdbuscpp/concatenator"};
auto concatenatorProxy = sdbus::createProxy(std::move(destination), std::move(objectPath));
// Let's subscribe for the 'concatenated' signals
sdbus::InterfaceName interfaceName{"org.sdbuscpp.Concatenator"};
concatenatorProxy->uponSignal("concatenated").onInterface(interfaceName).call([](const std::string& str){ onConcatenated(str); });
std::vector<int> numbers = {1, 2, 3};
std::string separator = ":";
// Invoke concatenate on given interface of the object
{
std::string concatenatedString;
concatenatorProxy->callMethod("concatenate").onInterface(interfaceName).withArguments(numbers, separator).storeResultsTo(concatenatedString);
assert(concatenatedString == "1:2:3");
}
// Invoke concatenate again, this time with no numbers and we shall get an error
{
try
{
concatenatorProxy->callMethod("concatenate").onInterface(interfaceName).withArguments(std::vector<int>(), separator);
assert(false);
}
catch(const sdbus::Error& e)
{
std::cerr << "Got concatenate error " << e.getName() << " with message " << e.getMessage() << std::endl;
}
}
// Give sufficient time to receive 'concatenated' signal from the first concatenate invocation
sleep(1);
return 0;
}
Client端代码解读:
便捷层API的实现与前文基本层相比,在注册方法、调用方法或发出信号时,多行代码已缩减为简单的一行代码,回调函数类型、相关参数及其类型检查,以及 D-Bus 消息的序列化和反序列化完全由编译器生成。
提示:uponSignal(...).call()
信号订阅函数还有个带参数return_slot_t
的重载,返回一个插槽对象。
根据官方建议,更希望用户使用在基本层API之上的便捷层API,当然最佳情况下应该使用还在便捷层之上的C++绑定层API,这样执行一个远程回调就会像在本地调用一个对象的函数一样简单,详见sdbus-c++中文版使用说明(四)——C++绑定层API。
注意:如何调试信号处理函数的回调?
默认情况下,若信号标签不匹配,信号处理的回调函数(示例中的onConcatenated
)就不会被调用,即该信号会被默默丢弃。如果想要进一步确认信号匹配问题,可以在参数列表的首部添加一个std::optional<sdbus::Error>
,这样sdbus-c++在调用到信号处理回调函数时,信号匹配正常则将这个参数设置为空,否则会产生一个Error
对象,例如下面这个修改,将Client端的信号处理回调中参数类型改为int
,这会因为实际收到了string
类型的信号而造成匹配错误:void onConcatenated(std::optional<sdbus::Error> e, int wrongParameter) { assert(e.has_value()); assert(e->getMessage() == "Failed to deserialize a int32 value"); }
参数类型不匹配是客户端没有收到信号反馈的主要原因,我们可以使用dbus-monitor查看总线上的信号,使用上述基于std::optionalsdbus::Error的回调变体,可以轻松地检查确认具体原因。
提示:如何在便捷层API访问D-Bus消息?
便捷 API 隐藏了 D-Bus 消息的级别,但这些消息会携带一些实现中可能需要的附加信息,例如发送者的名称,或有关凭据信息。在sdbud-c++中,我们可以在以下位置访问相应的 D-Bus 消息:
- 方法实现回调处理程序(服务器端)
- 属性集实现回调处理程序(服务器端)
- 信号回调处理程序(客户端)
IObject
和都IProxy
提供了getCurrentlyProcessedMessage()
方法。该方法旨在从回调处理程序中调用,它返回指向导致调用处理程序的相应 D-Bus 消息的指针。只要执行流不离开回调处理程序,该指针就有效(可取消引用)。当从其他上下文/线程调用时,指针可能为零或非零,并且其取消引用是未定义的行为。
以下是1.1 同步Server端修改后的部分内容,用于打印出方法调用发送者的名称:auto concatenate = [&concatenator](const std::vector<int> numbers, const std::string& separator) { const auto* methodCallMsg = concatenator->getCurrentlyProcessedMessage(); std::cout << "Sender of this method call: " << methodCallMsg.getSender() << std::endl; /*...*/ };
1.3 异步Server端
异步方法回调函数的参数列表中,除了需要输入参数外,还需要接收一个结果对象参数,这个参数的要求如下:
- 结果持有者的类型为
Result<Types...>&&
,其中Types...
是方法输出参数类型的列表。 - 结果对象必须是右值引用所采用的回调的第一个物理参数。
Result
类模板是仅移动的。 - 回调本身在物理上是一个返回
void
的函数。 - 方法输入参数是按值而不是按常量引用来获取,因为我们通常希望将它们
std::move
传递给工作线程。move通常比copy节省得多,而且这是惯用的方式,对于不可移动类型,这将会退为copy。
因此回调函数concatenate的函数类型将从std::string concatenate(const std::vector<int32_t>& numbers, const std::string& separator)
更改为void concatenate(sdbus::Result[std::string](std::string)&& result, std::vector<int32_t> numbers, std::string separator)
:
void concatenate(sdbus::Result<std::string>&& result, std::vector<int32_t> numbers, std::string separator) override
{
// Launch a thread for async execution...
std::thread([this, methodResult = std::move(result), numbers = std::move(numbers), separator = std::move(separator)]()
{
// Return error if there are no numbers in the collection
if (numbers.empty())
{
// Let's send the error reply message back to the client
methodResult.returnError({"org.sdbuscpp.Concatenator.Error", "No numbers provided"});
return;
}
std::string result;
for (auto number : numbers)
{
result += (result.empty() ? std::string() : separator) + std::to_string(number);
}
// Let's send the reply message back to the client
methodResult.returnResults(result);
// Emit the 'concatenated' signal with the resulting string
this->emitConcatenated(result);
}).detach();
}
异步Server端的代码解读:
Result
是一个代表方法未来结果的便捷类,与基本层API实现异步Server端时类似,我们在便捷层API可以通过这个Result类向客户端返回该方法的执行结果:
returnResults()
:返回正常的执行结果returnError()
:返回出现的Error消息与同步Server端的实现相比,异步Server端在对象上注册方法的方式(
implementedAs()
的使用)保持不变,其他内容也无需更改。
1.4 异步Client端
在便捷 API 级别,调用语句是以callMethodAsync()开头,然后通过以下面两种语句作为结束,分别对应了实现客户端异步调用的两种方法:
1. 基于回调(callback-based):使用uponReplyInvoke()
结束调用:
int main(int argc, char *argv[])
{
/* ... */
auto callback = [](std::optional<sdbus::Error> error, const std::string& concatenatedString)
{
if (!error) // No error
std::cout << "Got concatenate result: " << concatenatedString << std::endl;
else // We got a D-Bus error...
std::cerr << "Got concatenate error " << error->getName() << " with message " << error->getMessage() << std::endl;
}
// Invoke concatenate on given interface of the object
{
concatenatorProxy->callMethodAsync("concatenate").onInterface(interfaceName).withArguments(numbers, separator).uponReplyInvoke(callback);
// When the reply comes, we shall get "Got concatenate result 1:2:3" on the standard output
}
// Invoke concatenate again, this time with no numbers and we shall get an error
{
concatenatorProxy->callMethodAsync("concatenate").onInterface(interfaceName).withArguments(std::vector<int>{}, separator).uponReplyInvoke(callback);
// When the reply comes, we shall get concatenation error message on the standard error output
}
/* ... */
return 0;
}
方法1-异步Client端的代码解读:
该语句需要传入一个返回void的回调处理程序,它至少接受一个参数:std::optional<sdbus::Error>
,后续参数对应了 D-Bus 方法的输出。回调函数的error用法与基础层API中5.4 异步Client端部分的方法2相同:
error
参数为空:表示调用时没有发生 D-Bus 错误,后续参数是有效的 D-Bus 方法返回值。error
参数非空:表示调用期间发生了错误(后续参数只是默认构造的),底层Error
实例会向我们提供错误名称和消息。
提示:该函数返回
sdbus::PendingAsyncCall
对象(非拥有、但可用于观察本次调用的句柄),它可用于查询调用是否仍在进行中,以及取消调用。
提示:
uponReplyInvoke(callback, sdbus::return_slot);
这是一个带return_slot
参数的变体,它返回插槽对象(即持有本次异步调用的 RAII 句柄),这使客户端成为一个待处理异步调用的所有者,释放句柄意味着取消调用。
2. 基于std::future(Future-based):使用getResultAsFuture()
结束调用:
...
auto future = concatenatorProxy->callMethodAsync("concatenate").onInterface(interfaceName).withArguments(numbers, separator).getResultAsFuture<std::string>();
try
{
auto concatenatedString = future.get(); // This waits for the reply
std::cout << "Got concatenate result: " << concatenatedString << std::endl;
}
catch (const sdbus::Error& e)
{
std::cerr << "Got concatenate error " << e.getName() << " with message " << e.getMessage() << std::endl;
}
...
方法2-Client端的代码解读:
这是一个模板函数,需要以 D-Bus 方法返回的类型作为列表(如果是返回值为void
的方法,则为空列表),该函数返回一个std::future
对象,当方法异步执行后,在没有异常发生时该对象将包含返回值对应的回复消息,在调用发生异常时返回Error
(由std::future::get()
抛出sdbus::Error
)。
对于调用没有发生异常的情况,该函数返回的std::future
对象可以包含:
- void:对应返回为
void
的 D-Bus 方法;- 单一类型:对应返回单一值的 D-Bus 方法;
std::tuple
:该结构内对应保存具有多个返回值的 D-Bus 方法。
2. D-Bus属性(Property)的同步/异步读写
sdbus-c++在便捷层API考虑了对D-Bus属性读写操作的方便,当然也包括了C++绑定层API使用说明(待发布)。下面假设远程 D-Bus 对象在接口org.sdbuscpp.Concatenator
下提供了u类型(对应C++的uint32_t
类型)的属性status
。
2.1 读取属性
1. IProxy::getProperty()
:同步读取属性的方法。使用示例:
uint32_t status = proxy->getProperty("status").onInterface("org.sdbuscpp.Concatenator");
2. IProxy::getPropertyAsync()
:异步读取属性的方法。(无论基于回调,还是基于std::future的异步实现,均支持调用该方法异步读取D-Bus属性。使用示例:
// 基于回调
auto callback = [](std::optional<sdbus::Error> /*error*/, sdbus::Variant value)
{
std::cout << "Got property value: " << value.get<uint32_t>() << std::endl;
};
uint32_t status = proxy->getPropertyAsync("status").onInterface("org.sdbuscpp.Concatenator").uponReplyInvoke(std::move(callback));
// 基于std::future
std::future<sdbus::Variant> statusFuture = object.getPropertyAsync("status").onInterface("org.sdbuscpp.Concatenator").getResultAsFuture();
...
std::cout << "Got property value: " << statusFuture.get().get<uint32_t>() << std::endl;
3. IProxy::getAllProperties()
:以同步方式获取全部D-Bus属性的方法。
4. getAllPropertiesAsync()
:以异步方式获取全部D-Bus属性的方法。
2.2 修改属性
1. IProxy::setProperty()
:同步修改D-Bus属性的方法。使用示例:
uint32_t status = ...;
proxy->setProperty("status").onInterface("org.sdbuscpp.Concatenator").toValue(status);
2. IProxy::setPropertyAsync()
:异步修改属性的方法。使用示例:
// 基于回调
auto callback = [](std::optional<sdbus::Error> error { /*... Error handling in case error contains a value...*/ };
uint32_t status = proxy->setPropertyAsync("status").onInterface("org.sdbuscpp.Concatenator").toValue(status).uponReplyInvoke(std::move(callback));
// 基于std::future
std::future<void> statusFuture = object.setPropertyAsync("status").onInterface("org.sdbuscpp.Concatenator").getResultAsFuture();
DBus专栏导航:
D-Bus理论基础
Linux系统DBus工具的使用
sdbus-c++中文版使用说明(一)——概括介绍与编译
sdbus-c++中文版使用说明(二)——基础层API
sdbus-c++中文版使用说明(三)——便捷层API
sdbus-c++中文版使用说明(四)——C++绑定层API