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>

其中 interfacename 属性写文件名。

接下来里面的是信号,这里示例的有两个信号,一个名为「message」,另一个名为「action」,表示聊天的消息以及成员的动作(加入 / 退出)。

每个信号里面包含了信号的参数。一个典型的消息需要有发出者和消息内容本身,所以需要两个参数:name属性是可选的,但是为了标记,最好还是写上,「name」指的是成员的名字,「content」指的是消息内容;后面的 types,指字符串;directionout

这个 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)));