在Qt中使用DBus

基本概念

DBus是基于套接字的一种高级进程间通信机制,这主要是指套接字虽然可以用于进程间通信,但是为了保证机制的灵活性,导致其使用不够方便,使用者往往需要在其之上封装一套自己的协议,以方便程序代码使用,DBus可以简单理解为一种比较完善的协议封装。

DBus中主要最主要的概念为消息总线,总线往往对应于一个DBus后台进程。在Linux系统中常见的总线有system和session两条总线,其中system总线主要用于系统级别的daemon和程序间的通信(system级别的总线接入需要特定的权限);session总线主要用于session内的进程间互相传递消息使用。

连接到总线的进程可通过总线接收或传递消息,以达到通信的目的。一般情况下,对外提供服务的服务端程序连接至总线,并注册自己的服务,以供客户端程序连接和使用。程序注册DBus服务需指定服务名、对象路径和接口名称,其中服务名用于标识服务的唯一性;对象路径标识注册对象的唯一性(一个服务可以注册多个对象);而接口名称则定义了一组接口(包括函数名、属性和信号),同样的,一个对象下可以包含多组接口定义。

在现有的DBus协议和实现中,每个对象会自动包含org.freedesktop.DBus.Introspectable和org.freedesktop.Dbus.Properties接口,其中前者主要提供Introspect方法,可以将接口的信息输出结构化的描述数据(xml,后面会使用到),后者则主要提供Get、GetAll、Set方法和PropertiesChanged信号,用来获取、设置对象的属性以及接收属性变化的信号。

总线收到消息时,根据消息类型的不同进行不同的操作,如进行单播、多播还是广播等。DBus中常见的消息类型有:

1.method_call:函数调用;
2.method_return:函数调用返回;
3.error:函数调用异常;
4.signal:通知。

以上为DBus最基本的一些知识,如需了解更多详细信息可以参考网上的一些资料。下面介绍在Qt中如何使用DBus。

在Qt中使用DBus

注册服务

在Qt中注册DBus服务主要分为以下几个步骤:

1.创建一个服务提供类;
2.使用QDBusConnection类注册服务;
3.使用QDBusConnection类注册服务对象;

下面的例子代码创建了一个服务类Server,并提供了Hello方法和Tick信号:

#include <QObject>

class Server : public QObject
{
    Q_OBJECT
public:
    explicit Server(QObject *parent = 0);

signals:
    void Tick();

public slots:
    QString Hello();
};
#include "server.h"

#include <QTimer>

Server::Server(QObject *parent)
    : QObject(parent)
{
    QTimer *timer = new QTimer(this);
    timer->setInterval(1000);
    timer->setSingleShot(false);
    connect(timer, &QTimer::timeout, this, &Server::Tick);
}

QString Server::Hello()
{
    return "World";
}

在main函数中,使用QDBusConnection::sessionBus()方法获取session总线对象,并使用这个对方注册DBus服务和对象:

Server s;

bool result;
result = QDBusConnection::sessionBus().registerService("com.deepin.ch05");
if (!result) {
    qWarning() << "failed to register dbus service";
}

QDBusConnection::RegisterOptions options = QDBusConnection::ExportAllSlots \
                                            | QDBusConnection::ExportAllSignals;
result = QDBusConnection::sessionBus().registerObject("/com/deepin/ch05",
                                                        "com.deepin.Ch05", &s,
                                                        options);
if (!result) {
    qWarning() << "failed to register dbus object";
}

细心的读者会发现在上面的代码中,我们在registerObject函数中options中使用了ExportAllSlots和ExportAllSignals,这主要是利用Qt的MetaObject技术把槽函数和信号直接暴露在DBus服务上,使用非常方便。

运行程序,正常情况下DBus服务就注册成功了,这时候可以使用一下命令测试我们的服务:

$ qdbus com.deepin.ch05 /com/deepin/ch05 com.deepin.Ch05.Hello

输出“World”代表我们的服务运转正常。

使用服务

在Qt中调用DBus服务提供的方法也比较简单,只需使用QDBusInterface类即可:

QDBusInterface ifce("com.deepin.ch05", "/com/deepin/ch05", "com.deepin.Ch05");
QDBusMessage msg = ifce.call("Hello");
QString ret = msg.arguments().at(0).toString();
qDebug() << ret;

构造好QDbusInterface对象后,使用call函数同步调用DBus方法,或者可选地使用asyncCall异步调用DBus方法,便可以完成函数调用,这在简单使用单个DBus服务方法的时候比较适用。但是,这种方式也有一定的局限性,比如QDBusInterface类却无法接收服务端的信号,如果要接收信号则需要引入QDbusConnection等类、需要手动拆解函数返回值等。

代码生成

上面介绍的手动注册服务和使用服务,虽然也能满足一部分DBus使用需求,但是其代码无法复用、使用不方便的问题也很明显。另外,对于同样一个服务和接口,接口信息是相同的,有没有办法通过一种描述文件,同时生成服务端和客户端接口代码呢?答案是有的。

前面提到每个DBus对象都有一个org.freedesktop.DBus.Introspectable接口,提供Introspect方法可以提取接口的描述信息,我们可以按照这种格式编写自己的xml描述文件,用来生成客户端和服务端代码,以方便使用。针对上面我们的例子服务,我们编写的xml文件内容如下:

<interface name="com.deepin.Ch05">
    <method name="Hello">
        <arg direction="out" type="s"/>
    </method>
    <signal name="Tick">
</interface>

顶层元素为interface,name属性表明接口名称;interface元素的子元素为方法、属性和信号:method元素表示方法、property元素表示属性、signal元素表示接口中的信号,其各自都有name属性表示其名称,另外各自又包含不同的子元素可以用来表示参数,以及对参数进行注解。

定义好上面的描述文件后,在服务端程序的pro文件中添加如下内容:

DBUS_ADAPTORS += ../com.deepin.ch05.xml

以上内容会在qmake执行时自动生成ch05_adaptor.h和ch05_adaptor.cpp两个文件,其内容分别为:

/*
 * This file was generated by qdbusxml2cpp version 0.8
 * Command line was: 
 *
 * qdbusxml2cpp is Copyright (C) 2016 The Qt Company Ltd.
 *
 * This is an auto-generated file.
 * This file may have been hand-edited. Look for HAND-EDIT comments
 * before re-generating it.
 */

#ifndef CH05_ADAPTOR_H
#define CH05_ADAPTOR_H

#include <QtCore/QObject>
#include <QtDBus/QtDBus>
QT_BEGIN_NAMESPACE
class QByteArray;
template<class T> class QList;
template<class Key, class Value> class QMap;
class QString;
class QStringList;
class QVariant;
QT_END_NAMESPACE

/*
 * Adaptor class for interface com.deepin.Ch05
 */
class Ch05Adaptor: public QDBusAbstractAdaptor
{
    Q_OBJECT
    Q_CLASSINFO("D-Bus Interface", "com.deepin.Ch05")
    Q_CLASSINFO("D-Bus Introspection", ""
"  <interface name=\"com.deepin.Ch05\">\n"
"    <method name=\"Hello\">\n"
"      <arg direction=\"out\" type=\"s\"/>\n"
"    </method>\n"
"    <signal name=\"Tick\"/>\n"
"  </interface>\n"
        "")
public:
    Ch05Adaptor(QObject *parent);
    virtual ~Ch05Adaptor();

public: // PROPERTIES
public Q_SLOTS: // METHODS
    QString Hello();
Q_SIGNALS: // SIGNALS
    void Tick();
};

#endif
/*
 * This file was generated by qdbusxml2cpp version 0.8
 * Command line was: 
 *
 * qdbusxml2cpp is Copyright (C) 2016 The Qt Company Ltd.
 *
 * This is an auto-generated file.
 * Do not edit! All changes made to it will be lost.
 */

#include "ch05_adaptor.h"
#include <QtCore/QMetaObject>
#include <QtCore/QByteArray>
#include <QtCore/QList>
#include <QtCore/QMap>
#include <QtCore/QString>
#include <QtCore/QStringList>
#include <QtCore/QVariant>

/*
 * Implementation of adaptor class Ch05Adaptor
 */

Ch05Adaptor::Ch05Adaptor(QObject *parent)
    : QDBusAbstractAdaptor(parent)
{
    // constructor
    setAutoRelaySignals(true);
}

Ch05Adaptor::~Ch05Adaptor()
{
    // destructor
}

QString Ch05Adaptor::Hello()
{
    // handle method call com.deepin.Ch05.Hello
    QString out0;
    QMetaObject::invokeMethod(parent(), "Hello", Q_RETURN_ARG(QString, out0));
    return out0;
}

在使用处,即main函数中生成Ch05Adaptor对象,其构造函数的parent参数使用服务类对象即Server类对象,这样对DBus函数的调用就会自动调用Server类的函数,Server类的信号触发会自动反应到DBus服务的信号上。此处需要注意的是Server类提供的函数名称、属性名称以及信号名称需跟接口描述文件中的一致。

另外,在注册DBus对象时需注意,注册的对象仍然为原来的Server对象,但是注册的选项使用默认的ExportAdaptors:

result = QDBusConnection::sessionBus().registerObject("/com/deepin/ch05",
                                                        "com.deepin.Ch05", &s,
                                                        QDBusConnection::ExportAdaptors);

在客户端程序的pro文件中,同样添加如下内容:

DBUS_INTERFACES += ../com.deepin.ch05.xml

以上内容会使qmake执行过程中生成DBus调用相关的代理类ch05_interface.h和ch05_interface.cpp,内容分别如下:

/*
 * This file was generated by qdbusxml2cpp version 0.8
 * Command line was: 
 *
 * qdbusxml2cpp is Copyright (C) 2016 The Qt Company Ltd.
 *
 * This is an auto-generated file.
 * Do not edit! All changes made to it will be lost.
 */

#ifndef CH05_INTERFACE_H
#define CH05_INTERFACE_H

#include <QtCore/QObject>
#include <QtCore/QByteArray>
#include <QtCore/QList>
#include <QtCore/QMap>
#include <QtCore/QString>
#include <QtCore/QStringList>
#include <QtCore/QVariant>
#include <QtDBus/QtDBus>

/*
 * Proxy class for interface com.deepin.Ch05
 */
class ComDeepinCh05Interface: public QDBusAbstractInterface
{
    Q_OBJECT
public:
    static inline const char *staticInterfaceName()
    { return "com.deepin.Ch05"; }

public:
    ComDeepinCh05Interface(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent = 0);

    ~ComDeepinCh05Interface();

public Q_SLOTS: // METHODS
    inline QDBusPendingReply<QString> Hello()
    {
        QList<QVariant> argumentList;
        return asyncCallWithArgumentList(QStringLiteral("Hello"), argumentList);
    }

Q_SIGNALS: // SIGNALS
    void Tick();
};

namespace com {
  namespace deepin {
    typedef ::ComDeepinCh05Interface Ch05;
  }
}
#endif

/*
 * This file was generated by qdbusxml2cpp version 0.8
 * Command line was: 
 *
 * qdbusxml2cpp is Copyright (C) 2016 The Qt Company Ltd.
 *
 * This is an auto-generated file.
 * This file may have been hand-edited. Look for HAND-EDIT comments
 * before re-generating it.
 */

#include "ch05_interface.h"
/*
 * Implementation of interface class ComDeepinCh05Interface
 */

ComDeepinCh05Interface::ComDeepinCh05Interface(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent)
    : QDBusAbstractInterface(service, path, staticInterfaceName(), connection, parent)
{
}

ComDeepinCh05Interface::~ComDeepinCh05Interface()
{
}

在使用处,通过命名空间使用生成的Ch05代理类,方便地调用函数和接收信号:

com::deepin::Ch05 client("com.deepin.ch05", "/com/deepin/ch05", QDBusConnection::sessionBus());

QDBusPendingReply<QString> ret = client.Hello();
ret.waitForFinished();
qDebug() << ret.value();

QObject::connect(&client, &com::deepin::Ch05::Tick, [] {
    qDebug() << "tick";
});

代理类的函数调用默认为异步调用,同步等待需要使用上面代码中的waitForFinished函数。或者可以直接将函数调用赋值给最终结果,而不是QDBusPendingReply,例如:

QString ret = client.Hello();
qDebug() << ret;

上面的代码与前面的代码运行效果一致。异步调用可以通过QDBusPendingCallWatcher进行监听,在结果可用时进行处理,比较简单此处不再赘述。

发表评论

电子邮件地址不会被公开。 必填项已用*标注