sdbus-c++使用说明
1. 前言
本章节开始,将在前文D-Bus理论基础和linux系统DBus工具的使用的基础上,深入学习和理解sdbus-c++库的使用方法。
特别声明:
本系列文章参考了官方英文说明文档,同时从便于研发阅读的角度考量,重构了官方API相关的文章组织结构,若文中有内容的翻译存在歧义或错误,望及时评论指正。
参考链接:
本文内容对应的是sdbus-c+±2.0.0版本(官方文档可能会随升级发生变动):
using-sdbus-c++.md
2. 介绍
sdbus-c++ 是一个基于sd-bus构建的 C++ D-Bus 库(即sd-bus的包装器),sd-bus 是systemd项目中实现的轻量级 D-Bus 客户端库。它在更高的抽象级别上提供 D-Bus 功能,尽管 sdbus-c++ 涵盖了大部分 sd-bus API,但它尚未完全涵盖所有 sd-bus API 细节。重点放在最广泛使用的功能上:D-Bus 连接、对象、代理、同步和异步方法调用、信号和属性。
3. 编译
3.1 源文件下载
编译所需的依赖库包括sigc++、systemd。本文使用了sigc+±3.6.0版本以及sdbus-c+±2.0.0版本:
libsigcplusplus-3.6.0.tar.gz
sdbus-cpp-2.0.0.tar.gz
3.2 解决systemd依赖
注意:
如果想交叉编译,亲测过程并不顺利,依赖的sigc++、expat、systemd、libcap全都要先进行交叉编译,期间涉及到手动修改CMakeList,尤其systemd更是使用meson、ninja编译工具,本文32位arm的交叉编译结果,最后也是通过buildroot构建系统后,从系统里提出依赖的动态库直接使用的。
通常linux系统自带了systemd。如果没有,我们可以采取以下两种方式:
- systemd相当独立,我们可以单独编译systemd的动态库;
- sdbus-c++提供了更便捷的方式,在cmake配置中启用
SDBUSCPP_BUILD_LIBSYSTEMD,这是在非 systemd 环境中构建、分发和使用 sdbus-c++的最方便、最有效的方法。只需确保具有 libsystemd 构建过程所需的所有依赖项:ninja、git、gperf、libmount、libcap、librt,同时还可以通过设置SDBUSCPP_LIBSYSTEMD_VERSION标志来调整要采用的 systemd 版本(默认值为 242)。
3.3 编译sigc++
首先确保系统已拥有cmake编译所需的依赖库。本文创建了文件夹libsigcxx-3.6.0作为自定义的安装路径:
mkdir cmake_build
mkdir libsigcxx-3.6.0
cd cmake_build
cmake .. -DCMAKE_INSTALL_PREFIX="/home/softwares/libsigcplusplus-3.6.0/libsigcxx-3.6.0"
make -j8
make install
3.4 编译sdbus-c++
本文创建了文件夹sdbus-cpp-2.0.0作为自定义安装路径,并在cmake配置中启用SDBUSCPP_BUILD_CODEGEN用于生成xml2cpp工具:
mkdir build
mkdir sdbus-cpp-2.0.0
cd build
cmake .. -DCMAKE_INSTALL_PREFIX="/home/softwares/sdbus-cpp-2.0.0/sdbus-cpp-2.0.0" -DSDBUSCPP_BUILD_CODEGEN=ON
make -j8
make install
温馨提示:
虽然本文并未遇到,但如果编译sdbus-c++报错系统找不到sigc++依赖,可以指定上面自己编译的sigc++的pkgconfig路径:
export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/home/softwares/libsigcplusplus-3.6.0/libsigcxx-3.6.0/lib/pkgconfig
3.5 编译好的依赖库
以下是可以直接供linux x86_64、linux 32bit ARM使用的依赖库,对于linux x86_64平台,需要确保已安装libdbus-1-dev、libexpat1-dev、libpopt-dev库,且系统内拥有systemd。
编译好的sdbus-c++动态库及其依赖库
4. 设计方案
4.1 头文件与命名空间
在sdbus-c++库中,所有头文件全部定义在sdbus-c++子目录下,所有公共类型和函数都位于sdbus命名空间中。
我们可以选择只是单独导入所需的头文件,例如:
#include <sdbus-c++/IConnection.h>
#include <sdbus-c++/IProxy.h>
也可以直接导入总的头文件sdbus-c++.h,这样就一次性导入了所有头文件:
#include <sdbus-c++/sdbus-c++.h>
温馨提示:
在sdbus命名空间中,也许我们会看到同时存在sdbus::ServiceName和sdbus::BusName,可问题是sdbus-c++库中这两个实体画上了等号,这段定义在sdbus-c++/IConnection.h中,如下所示。
在其他DBus库中可能也存在类似情况,将Bus Name说成Service,但不要因此就混淆两者的概念,在前面文章Service章节曾重点解答两者概念上的困惑,而在特定库或框架中有时会采用这种简化的方式来统一概念或简化接口。实际上只需记住一点:DBus中的Service不是在编写代码层面存在的概念,而是与系统systemd管理进程具有相同的含义,代码里画等号只为方便。
namespace sdbus {
class Message;
class ObjectPath;
class BusName;
using ServiceName = BusName;//这里给两者画了等号
}
4.2 错误信号类型
该库中定义了sdbus::Error这一类型用作 sdbus-c++ 中信号错误的异常,它是两种错误类型的载体,携带错误名称和错误消息。这两种错误类型具体为:
- D-Bus 相关错误,例如调用超时、套接字分配失败等。这些是由 D-Bus 库或 D-Bus 守护进程本身引发的。
- 用户定义的错误,即从远程方法发出并返回给调用者的错误。因此,这种通常是在sdbus-c++的客户端产生。
4.3 API的UML建模分析
sdbus-c++库中最主要的实体关系如下图所示,基于D-Bus理论基础之上可对该库的API进行基本了解:

1. IConnection表示的就是D-Bus总线连接这一概念。使用时可以连接到系统总线或会话总线,每个连接都要分配一个公共名(well-known Bus Name),在总线连接上可以运行 I/O 事件循环。
2. IObject表示D-Bus中对象的概念。其职责包括:
(1) 注册(可能多个)接口以及接口上的方法、信号和属性;
(2) 发射信号;
3. IProxyObject表示D-Bus中代理的概念,它是从客户端的角度来看的。它的职责是:
(1) 以同步和异步方式调用相应对象的远程方法;
(2) 注册信号处理程序;
4. Message类表示一条D-Bus中的消息。从类派生出不同类型的消息:
(1) MethodCall:无论是同步还是异步方法调用,带有序列化参数;
(2) MethodReply:具有序列化的返回值;
(3) Signal:带有序列化参数;
(4) PropertySetCall:需带有将要设置的序列化参数(值);
(5) PropertyGetReply:返回成员属性值;
(6) PlainMessage:(根据源码的解释)该类型表示上述任何一种消息类型,或仅表示作为数据容器的消息。
4.4 关于线程安全
根据官方说明,sdbus-c++符合线程感知(thread-ware),该库对于自身提供的API接口一定程度上可以保证线程安全:
sdbus-c++ 在设计上完全符合线程感知(thread-ware)。虽然 sdbus-c++ 通常不是线程安全的,但在某些情况下 sdbus-c++ 从设计上提供并保证 API 级线程安全。同时从多个线程执行下列操作是安全的(下列每个单独的操作,而非它们之间的组合):
- 同时创建或销毁不同的
Object/Proxy实例(即使在已运行事件循环的共享连接上,见下文)。此处的创建是指完整的构造序列、方法/信号/属性回调的注册以及Object/Proxy的导出,以便准备好发出/接收消息。此序列必须在一个线程的上下文中完全完成。- 在
Object实例上创建并发送异步方法答复。- 在
Object实例上创建并发射信号。- 在
Proxy实例上创建和发送方法调用(同步和异步)。(但通常我们的线程最好使用自己的代理独占实例,以最大限度地减少共享状态和竞争。)sdbus-c++ 的设计使得上述所有操作在运行事件循环(通常在单独的线程中)的连接上也是线程安全的。这是一种内部线程安全。例如,信号到达并恰好在Proxy实例中循环处理,而用户却在其应用程序线程中销毁该实例。用户无法明确控制这些情况(也许用户可以做到,但这将导致API 变得困难且繁琐)。
在多个线程内显式组合调用以上操作引发的线程安全问题,并非是在 sdbus-c++ 设计上的线程安全,用户应通过其设计确保这些情况永远不会发生。例如,在一个线程中销毁一个对象实例,而在另一个线程中对其发送信号,这显然不是线程安全的。在这种特定情况下,用户应在其应用程序中确保所有线程在一个线程继续删除特定实例之前停止对该实例的操作。
4.5 多层API
sdbus-c++提供了多层API供用户使用:
- 基础层:是 sd-bus 上的一个简单包装层,使用 C++ 原生的机制(例如消息数据的序列化/反序列化);
- 便捷层:建立在基础层之上,旨在减轻用户不必要的细节,使用户能够编写更短、更安全、更具表现力的代码;
- C++ 绑定层:最高级别的 API ,使得 D-Bus RPC 调用完全看起来像对本地对象的本机 C++ 调用。只需借助自己附带的xml2cpp工具,将 XML 格式的D-Bus IDL转换为适配器和代理部分的C++ 绑定。(官方推荐用户使用本层API)
sdbus-c++中每层API都支持服务端异步执行D-Bus方法,并将控制权快速返回给 D-Bus 调度线程,从而避免事件循环中某个任务的执行时间过长导致了其他客户端或任务被阻塞。
sdbus-c++ 还支持客户端(代理)端的异步方法,即客户端发出 D-Bus 方法调用后不会因为等待回复而阻塞当前线程,当回复到来时,要么在事件循环线程的上下文中调用给定的回调处理程序,要么将异步调用返回的未来对象设置为返回值。
在后面的文章中,将使用简单的连接器示例,分别介绍上述三层API的使用方法。
DBus专栏导航:
D-Bus理论基础
Linux系统DBus工具的使用
sdbus-c++中文版使用说明(一)——概括介绍与编译
sdbus-c++中文版使用说明(二)——基础层API
sdbus-c++中文版使用说明(三)——便捷层API
sdbus-c++中文版使用说明(四)——C++绑定层API
15万+

被折叠的 条评论
为什么被折叠?



