D-Bus 与 Qt
最近的操作系统实验要求写个进程间通信程序,我注意到了 D-Bus 这个可以双向通信的协议,它能够通知到接收方。
D-Bus 协议是 Unix 平台下的用于进程间通信(IPC,Inter-Process Communication)的协议,它也可用于远程过程调用(RPC,Remote Procedure Call)。
Qt 是一个广泛使用的图形界面库,它将系统的 D-Bus 库封装成为 QDbus
类,便于我们去使用。在官方文档中,这是一种推荐的进程间通信方法,不过它仅支持 Unix 平台(Linux 或 macOS)。
不过 macOS,和众多主流 Linux 操作系统还是有所不同的。虽然 macOS 属于 Unix 系的操作系统,但它没有预装 D-Bus 库,所以想要在 Qt 里面使用 D-Bus 协议进行进程间通信,首先需要手动安装 D-Bus 服务。
安装 D-Bus
如果你是个程序员,你的电脑上肯定安装了 Homebrew 或者其他的包管理工具,以提高效率。
下面就以 Homebrew 为例,安装 D-Bus。执行下面的安装命令。
brew install dbus
安装完成后,你能在 /opt/homebrew/Cellar/dbus
目录下找到它,这个目录之后会用到。
我安装的版本是 1.14.0,所以 D-Bus 库在 /opt/homebrew/Cellar/dbus/1.14.0
下,下面将以这个版本为例。
启动 D-Bus 服务
下面的两种方法,只需要看其中一种。
Homebrew Services
Homebrew 提供了方便的类似于 Linux 下 service 命令的服务管理功能,其实本质也是调用了 launchctl 进行管理。
执行下面的命令即可启动 D-Bus 服务。
brew services start dbus
如果出现下面的报错,说明之前已经启动过服务了。可以执行 brew services stop dbus
来停止服务。
Bootstrap failed: 5: Input/output error
Launchctl
Launchctl 是 macOS 原生的服务管理。
建立软连接到 ~/Library/LaunchAgents
目录。
ln -sfv /opt/homebrew/Cellar/dbus/1.14.0/org.freedesktop.dbus-session.plist ~/Library/LaunchAgents
执行下面的命令可以启动 D-Bus 服务。
launchctl load ~/Library/LaunchAgents/org.freedesktop.dbus-session.plist
执行下面的命令关闭 D-Bus 服务。
launchctl unload ~/Library/LaunchAgents/org.freedesktop.dbus-session.plist
如果有报错,可能是因为已经启动过服务了,可以关闭服务再启动试试。
备注
如果非 root 权限无法启动,可能是由于目录权限不足,可以使用 chown 命令来修改 Homebrew 目录的所有者。
sudo chown -R $(whoami) $(brew --prefix)/*
$(whoami)
变量指的是当前用户名。$(brew --prefix)
指的是 Homebrew 目录,我的是/opt/homebrew
。
添加外部库
按照官方文档的说法,macOS 下因为没有原生的 libdbus-1 库,所以需要自己引入。
刚才我们安装的 D-Bus 目录下面已经包含这些库啦,所以只需要在 Qt Creator 的项目中引入这些库就好。以我的为例,这个库的在 /opt/homebrew/Cellar/dbus/1.14.0/lib/libdbus-1.dylib
路径下。现在右键点击项目名称,点击「添加库…」,选择「外部库」。在「库文件」里填入库的路径,「包含路径」将会自动配置好,平台只选择 Mac 平台,接下来一路下一步就好啦。
试试看
在 Qt Creator 的「示例」中,有个名为「D-Bus Chat Example」的示例项目,打开它,安装上述方法进行配置。
如果你成功地运行了这个项目,弹出了窗口,说明一切都到位了,你的 QDBus 是可以正常运行的。
如果不幸没有配置好的话,控制台会有如下的输出,说明前面有哪一步出了问题。
Cannot connect to the D-Bus session bus.
Please check your system settings and try again.
在自己的项目中使用
以下内容为根据 Qt 示例的个人猜测,反正能用。
建立 D-BUS Object Introspection XML
模仿示例项目中的 org.example.chat.xml
文件,新建一个自己的,新建文件时选择「General」>「Empty File」,文件命名为 <组织类型>.<组织名>.<项目名>.xml
的格式,例如我新建一个 org.hawa130.dbuschat.xml
的文件,里面模仿示例文件写入内容。
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name="org.hawa130.dbuschat">
<signal name="message">
<arg name="name" type="s" direction="out"/>
<arg name="content" type="s" direction="out"/>
</signal>
<signal name="action">
<arg name="name" type="s" direction="out"/>
<arg name="content" type="s" direction="out"/>
</signal>
</interface>
</node>
其中 interface
的 name
属性写文件名。
接下来里面的是信号,这里示例的有两个信号,一个名为「message」,另一个名为「action」,表示聊天的消息以及成员的动作(加入 / 退出)。
每个信号里面包含了信号的参数。一个典型的消息需要有发出者和消息内容本身,所以需要两个参数:name
属性是可选的,但是为了标记,最好还是写上,「name」指的是成员的名字,「content」指的是消息内容;后面的 type
为 s
,指字符串;direction
为 out
。
这个 xml 的内容可以按照自己的需要写。可以参考简明文档以及更详细的官方文档,了解写 D-Bus xml 的详细知识。
项目配置
在你的 .pro
项目配置文件中,添加下面的内容,之后「添加外部库」。
QT += dbus
DBUS_ADAPTORS += org.hawa130.dbuschat.xml
DBUS_INTERFACES += org.hawa130.dbuschat.xml
引入头文件
接下来 Build 会产生头文件。名为 <项目名>_adaptor.h
以及 <项目名>_interface.h
。
以我的为例,在需要用到 D-Bus 的代码加入下面的头文件即可。
#include "dbuschat_adaptor.h"
#include "dbuschat_interface.h"
QDbus 的使用
可以理解成线程间的信号与槽。
可以参考示例项目的代码理解。
new ChatAdaptor(this);
QDBusConnection::sessionBus().registerObject("/", this);
org::example::chat *iface;
iface = new org::example::chat(QString(), QString(), QDBusConnection::sessionBus(), this);
// 可以将 iface 作为普通的 QObject 来举行信号与槽的连接
connect(iface, SIGNAL(message(QString,QString)), this, SLOT(messageSlot(QString,QString)));