QtDbus 編程


 

Client :: Method Call

方法1: 直接使用 Message 傳遞消息(Low-Level-API)

// 傳參數
QDBusMessage msg = QDBusMessage::createMethodCall("com.myabc.service",
                             "/", "com.myabc.interface", "setName");
msg << QString("Bright");
QDBusMessage response = QDBusConnection::sessionBus().call(msg);
 
// 獲取返回值
QDBusMessage msg = QDBusMessage::createMethodCall("com.myabc.service",
                             "/", "com.myabc.interface", "name");
QDBusMessage response = QDBusConnection::sessionBus().call(msg);
 
// 判斷 Method 是否被正確返回
if(response.type() == QDBusMessage::ReplyMessage)
{
    // QDBusMessage的arguments不僅可以用來存儲發送的參數,也用來存儲返回值
    // 這里取得 checkIn 的返回值
    QString name= response.arguments().takeFirst().toString();
}

方法2: 通過 DBusInterface 調用方法(同步+異步)

QDBusInterface interface("com.myabc.service", "/",
                         "com.myabc.interface",
                         QDBusConnection::sessionBus());
if(!interface.isValid())
{
    qDebug() << qPrintable(QDBusConnection::sessionBus().lastError().message());
    exit(1);
}
 
// 調用遠程對象的方法 setName()
interface.call("setName", "Bright");
 
// 調用 name() 並獲取返回值
QDBusReply<QString> reply = interface.call("name");
if(reply.isValid())
{
    QString value = reply.value();
    qDebug() << "value = " << value ;
}

interface::asyncCall( ) 異步調用

QDBusPendingCall async = interface->asyncCall("setName", "Brion");
// or use this: QDBusPendingReply<QString> reply = interface->asyncCall("RemoteMethod");
async.waitForFinished ();
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(async, this);
 
QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)),
                 this, SLOT(callFinishedSlot(QDBusPendingCallWatcher*)));
 
void MyClass::callFinishedSlot(QDBusPendingCallWatcher *call)
{
    QDBusPendingReply<QString> reply = *call;
    if (! reply.isError()) {
        QString name= reply.argumentAt<0>();
        qDebug() << "name = " << name;
    }
    call->deleteLater();
}

方法3: 從XML導入代理類

A. 使用工具qdbuscpp2xml從object.h生成XML文件:

qdbuscpp2xml -M test.h -o com.scorpio.test.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="com.scorpio.test.value">
    <method name="maxValue">
      <arg type="i" direction="out"/>
    </method>
    <method name="minValue">
      <arg type="i" direction="out"/>
    </method>
    <method name="value">
      <arg type="i" direction="out"/>
    </method>
  </interface>
</node>

B. 使用工具qdbusxml2cpp從XML文件生成繼承自QDBusInterface的類

qdbusxml2cpp com.scorpio.test.xml -p valueInterface

生成兩個文件:valueInterface.cpp 和 valueInterface.h  &  valueInterface.h文件:
/*
 * This file was generated by qdbusxml2cpp version 0.7
 * Command line was: qdbusxml2cpp com.scorpio.test.xml -p testInterface
 *
 * qdbusxml2cpp is Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
 *
 * This is an auto-generated file.
 * Do not edit! All changes made to it will be lost.
 */

#ifndef TESTINTERFACE_H_1526737677
#define TESTINTERFACE_H_1526737677

#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.scorpio.test.value
 */
class ComScorpioTestValueInterface: public QDBusAbstractInterface
{
    Q_OBJECT
public:
    static inline const char *staticInterfaceName()
    { return "com.scorpio.test.value"; }

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

    ~ComScorpioTestValueInterface();

public Q_SLOTS: // METHODS
    inline QDBusPendingReply<int> maxValue()
    {
        QList<QVariant> argumentList;
        return asyncCallWithArgumentList(QLatin1String("maxValue"), argumentList);
    }

    inline QDBusPendingReply<int> minValue()
    {
        QList<QVariant> argumentList;
        return asyncCallWithArgumentList(QLatin1String("minValue"), argumentList);
    }

    inline QDBusPendingReply<int> value()
    {
        QList<QVariant> argumentList;
        return asyncCallWithArgumentList(QLatin1String("value"), argumentList);
    }

Q_SIGNALS: // SIGNALS
};

namespace com {
  namespace scorpio {
    namespace test {
      typedef ::ComScorpioTestValueInterface value;
    }
  }
}
#endif
View Code
/*
 * This file was generated by qdbusxml2cpp version 0.7
 * Command line was: qdbusxml2cpp com.scorpio.test.xml -p testInterface
 *
 * qdbusxml2cpp is Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
 *
 * This is an auto-generated file.
 * This file may have been hand-edited. Look for HAND-EDIT comments
 * before re-generating it.
 */

#include "testInterface.h"

/*
 * Implementation of interface class ComScorpioTestValueInterface
 */

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

ComScorpioTestValueInterface::~ComScorpioTestValueInterface()
{
}
View Code

 調用Proxy類訪問Service如下:

#include <QCoreApplication>
#include <QDBusMessage>
#include <QDBusConnection>
#include <QDBusReply>
#include <QDBusInterface>
#include <QDebug>
#include "testInterface.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    // 初始化自動生成的Proxy類com::scorpio::test::value
    com::scorpio::test::value test("com.scorpio.test",
                                   "/test/objects",
                                   QDBusConnection::sessionBus());
    // 調用value方法
    QDBusPendingReply<int> reply = test.value();
    //qdbusxml2cpp生成的Proxy類是采用異步的方式來傳遞Message,
    //所以需要調用waitForFinished來等到Message執行完成
    reply.waitForFinished();
    if (reply.isValid())
    {
        int value = reply.value();
        qDebug() << QString("value =  %1").arg(value);
    }
    else
    {
        qDebug() << "value method called failed!";
    }

    return a.exec();
}

 

Subscriber :: Signal Catching

方法1:BusConnection捕獲信號

QDBusConnection::sessionBus().connect("com.brion.service", "/",
                                      "com.brion.interface",
                                      "ageChanged", this, 
                                      SLOT(onAgeChanged(int)));

方法2:通過Proxy/Interface捕獲信號

QDBusInterface *interface = new QDBusInterface("com.brion.service", "/", 
                                               "com.brion.interface",
                                               DBusConnection::sessionBus());

QObject::connect(&interface, SIGNAL(ageChanged(int)),  
                 object, SLOT(onAgeChanged(int)));

 

Server/Publisher :: Register Service

方式1:編寫服務類,並注冊服務和接口對象

class Person : public QObject
{
    Q_OBJECT

    Q_CLASSINFO("D-Bus Interface", "com.brion.interface")
public: explicit Person(QObject *parent = 0); signals: void nameChanged(QString); void ageChanged(int); public slots: QString name() const { return m_name; } // can't be reference void setName(QString name) { m_name = name; } int age() const { return m_age; } void setAge(int age) { m_age = age; } private: QString m_name; int m_age; }; // main.cpp #include <QtDBus/QDBusConnection> #include <person.h> int main(int argc, char *argv[]) { QApplication a(argc, argv); QDBusConnection sessionBus = QDBusConnection::sessionBus(); if (sessionBus.registerService("com.brion.service")) { sessionBus.registerObject("/", new Person(), QDBusConnection::ExportAllContents); } return a.exec(); }

示例2:PingPong Game

Publisher,直接繼承自 QDBusAbstractAdaptor

class Pong: public QDBusAbstractAdaptor
{
    Q_OBJECT
    Q_CLASSINFO("D-Bus Interface", "org.example.QtDBus.ComplexPong.Pong")
    Q_PROPERTY(QString value READ value WRITE setValue)
public:
    QString m_value;
    QString value() const;
    void setValue(const QString &newValue);

    Pong(QObject *obj) : QDBusAbstractAdaptor(obj)
    { }
signals:
    void aboutToQuit();
public slots:
    QDBusVariant query(const QString &query);
    Q_NOREPLY void quit();
};

//  啟動服務
int main(int argc, char **argv)
{
    QCoreApplication app(argc, argv);

    QObject obj;
    Pong *pong = new Pong(&obj);
    QObject::connect(&app, &QCoreApplication::aboutToQuit, pong, &Pong::aboutToQuit);
    pong->setProperty("value", "initial value");
    QDBusConnection::sessionBus().registerObject("/", &obj);

    if (!QDBusConnection::sessionBus().registerService(SERVICE_NAME)) {
        fprintf(stderr, "%s\n",
                qPrintable(QDBusConnection::sessionBus().lastError().message()));
        exit(1);
    }

    app.exec();
    return 0;
}

Subscriber端,監聽Publisher的信號:

class Ping: public QObject
{
    Q_OBJECT
public slots:
    void start(const QString &);
public:
    QFile qstdin;
    QDBusInterface *iface;
};

void Ping::start(const QString &name)
{
    if (name != SERVICE_NAME)
        return;

    // open stdin for reading
    qstdin.open(stdin, QIODevice::ReadOnly);

    // find our remote
    iface = new QDBusInterface(SERVICE_NAME, "/", "org.example.QtDBus.ComplexPong.Pong",
                               QDBusConnection::sessionBus(), this);
    if (!iface->isValid()) {
        fprintf(stderr, "%s\n",
                qPrintable(QDBusConnection::sessionBus().lastError().message()));
        QCoreApplication::instance()->quit();
    }

    connect(iface, SIGNAL(aboutToQuit()), QCoreApplication::instance(), SLOT(quit()));

    while (true) {
        printf("Ask your question: ");

        QString line = QString::fromLocal8Bit(qstdin.readLine()).trimmed();
        if (line.isEmpty()) {
            iface->call("quit");
            return;
        } else if (line == "value") {
            QVariant reply = iface->property("value");
            if (!reply.isNull())
                printf("value = %s\n", qPrintable(reply.toString()));
        } else if (line.startsWith("value=")) {
            iface->setProperty("value", line.mid(6));
        } else {
            QDBusReply<QDBusVariant> reply = iface->call("query", line);
            if (reply.isValid())
                printf("Reply was: %s\n", qPrintable(reply.value().variant().toString()));
        }

        if (iface->lastError().isValid())
            fprintf(stderr, "Call failed: %s\n", qPrintable(iface->lastError().message()));
    }
}


int main(int argc, char **argv)
{
    QCoreApplication app(argc, argv);

    if (!QDBusConnection::sessionBus().isConnected()) {
        fprintf(stderr, "Cannot connect to the D-Bus session bus.\n"
                "To start it, run:\n"
                "\teval `dbus-launch --auto-syntax`\n");
        return 1;
    }

    QDBusServiceWatcher serviceWatcher(SERVICE_NAME, QDBusConnection::sessionBus(),
                                       QDBusServiceWatcher::WatchForRegistration);

    Ping ping;
    QObject::connect(&serviceWatcher, &QDBusServiceWatcher::serviceRegistered,
                     &ping, &Ping::start);

    QProcess pong;
    pong.start("./complexpong");

    app.exec();
}

 

方式2:通過 XML 定義並轉換為Adapter對象發布服務

方法4: <DBusAdapter>
生成Adapter類的流程如下:

A. 使用工具 qdbuscpp2xml從test.h生成XML文件

qdbuscpp2xml -M test.h -o com.scorpio.test.xml

B. 編輯com.scorpio.test.xml,選擇需要發布的method,不需要發布的刪除。

C. 使用工具qdbusxml2cpp從XML文件生成繼承自QDBusInterface的類

qdbusxml2cpp com.scorpio.test.xml -i test.h -a valueAdaptor

生成兩個文件:valueAdaptor.cpp和valueAdaptor.h & valueAdaptor.h文件:

/*
 * This file was generated by qdbusxml2cpp version 0.7
 * Command line was: qdbusxml2cpp com.scorpio.test.xml -i test.h -a valueAdaptor
 *
 * qdbusxml2cpp is Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
 *
 * This is an auto-generated file.
 * This file may have been hand-edited. Look for HAND-EDIT comments
 * before re-generating it.
 */

#ifndef VALUEADAPTOR_H_1526742670
#define VALUEADAPTOR_H_1526742670

#include <QtCore/QObject>
#include <QtDBus/QtDBus>
#include "test.h"
class QByteArray;
template<class T> class QList;
template<class Key, class Value> class QMap;
class QString;
class QStringList;
class QVariant;

/*
 * Adaptor class for interface com.scorpio.test.value
 */
class ValueAdaptor: public QDBusAbstractAdaptor
{
    Q_OBJECT
    Q_CLASSINFO("D-Bus Interface", "com.scorpio.test.value")
    Q_CLASSINFO("D-Bus Introspection", ""
"  <interface name=\"com.scorpio.test.value\">\n"
"    <method name=\"maxValue\">\n"
"      <arg direction=\"out\" type=\"i\"/>\n"
"    </method>\n"
"    <method name=\"minValue\">\n"
"      <arg direction=\"out\" type=\"i\"/>\n"
"    </method>\n"
"  </interface>\n"
        "")
public:
    ValueAdaptor(QObject *parent);
    virtual ~ValueAdaptor();

public: // PROPERTIES
public Q_SLOTS: // METHODS
    int maxValue();
    int minValue();
Q_SIGNALS: // SIGNALS
};

#endif
View Code
/*
 * This file was generated by qdbusxml2cpp version 0.7
 * Command line was: qdbusxml2cpp com.scorpio.test.xml -i test.h -a valueAdaptor
 *
 * qdbusxml2cpp is Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
 *
 * This is an auto-generated file.
 * Do not edit! All changes made to it will be lost.
 */

#include "valueAdaptor.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 ValueAdaptor
 */

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

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

int ValueAdaptor::maxValue()
{
    // handle method call com.scorpio.test.value.maxValue
    int out0;
    QMetaObject::invokeMethod(parent(), "maxValue", Q_RETURN_ARG(int, out0));
    return out0;
}

int ValueAdaptor::minValue()
{
    // handle method call com.scorpio.test.value.minValue
    int out0;
    QMetaObject::invokeMethod(parent(), "minValue", Q_RETURN_ARG(int, out0));
    return out0;
}
View Code

調用Adaptor類注冊Object對象如下:

#include <QCoreApplication>
#include <QDBusConnection>
#include <QDebug>
#include <QDBusError>
#include "test.h"
#include "valueAdaptor.h"
 
int main(int argc, char *argv[]){
    QCoreApplication a(argc, argv);
    QDBusConnection connection = QDBusConnection::sessionBus();
 
    test object(60);
    //ValueAdaptor是qdbusxml2cpp生成的Adaptor類
    ValueAdaptor valueAdaptor(&object);
    if (!connection.registerService("com.scorpio.test"))
    {
        qDebug() << connection.lastError().message();
        exit(1);
    }
    connection.registerObject("/test/objects", &object);
    return a.exec();
}

 

其他:自啟動DBus服務項

D-Bus系統提供了一種機制可以在訪問某個service時,自動把應用程序運行起來。

需要在/usr/share/dbus-1/services下面建立com.scorpio.test.service文件,文件的內容如下:

[D-BUS Service]
Name=com.scorpio.test
Exec=/path/to/scorpio/test

在訪問test的method前,不必手動運行應用程序。


QtDBus 在 PyQt5 中的應用

常用類即其方法,請參考腦圖

定義服務類

這里提供一個實例代碼(PingPong在PyQt5中的實現):

import sys

from PyQt5.QtCore import QCoreApplication, pyqtSlot, QObject
from PyQt5.QtDBus import QDBusConnection, QDBusInterface, QDBusReply, QDBusAbstractAdaptor

class Pong(QObject):
    @pyqtSlot(str, result=str)
    def pong_method(self, args):
        print("Get a proxy-method call... the args is: ", args)
        return 'ping("%s") got called.' % args

    @pyqtSlot()
    def pong_method_without_params(self):
        print("Get a proxy-method call...")


if __name__ == "__main__":
    app = QCoreApplication(sys.argv)
    bus = QDBusConnection.sessionBus()  # sessionBus()
    if not bus.isConnected():
        raise Exception("Fail to connect to Session-Bus.")
    # register the service
    if not bus.registerService('org.example.QtDBus.PingPong'):  # 注冊 BusName
        raise Exception("Fail to register the service.")

    pong = Pong()  # 創建對象 -> dbus server object
    print("-->> ", isinstance(pong, QDBusAbstractAdaptor))
    bus.registerObject("/",  # this is the object-path, must be start from '/'
                        "pong.anything.namespace",  # this is the interface [choose to name it]
                        pong,  # this is the server obj, but you must prepare it before
                        # QDBusConnection.ExportAllSlots)  # 注冊所有slot作為proxy-method
                        QDBusConnection.ExportAllContents)
    sys.exit(app.exec_())

結合XML定義Adapter類

class Car(QObject):
    def turn_left(self, degree: int):
        print("The car turn left [{}] degree.".format(degree))

    def turn_right(self, degree: int):
        print("The car turn right [{}] degree.".format(degree))

    def turn_back(self):
        print("The car is turnning back.")


class CarInterface(QDBusAbstractAdaptor):
    Q_CLASSINFO("D-Bus Interface", 'org.HallGarden.Examples.Interface')
    Q_CLASSINFO("D-Bus Introspection", ''
            '  <interface name="org.HallGarden.Examples.Interface">\n'
            '    <method name="turn_left">\n'
            '      <arg direction="in" type="i"/>\n'
            '    </method>\n'
            '    <method name="turn_right">\n'
            '      <arg name="degree" direction="in" type="i"/>\n'
            '      <arg name="any" direction="out" type="i"/>\n'
            '    </method>\n'
            '    <method name="turn_back"/>\n'
            '  </interface>\n'
            '')

    def __init__(self, parent):
        super().__init__(parent)
        self.setAutoRelaySignals(True)

    @pyqtSlot(int)
    def turn_left(self, degree):
        self.parent().turn_left(degree)

    @pyqtSlot(int)
    def turn_right(self, degree):
        self.parent().turn_right(degree)
        return 30

    @pyqtSlot()
    def turn_back(self):
        self.parent().turn_back()


if __name__ == "__main__":
    app = QCoreApplication(sys.argv)

    car = Car()
    CarInterface(car)  # 裝飾car對象,而新生成的對象並沒實際應用

    connection = QDBusConnection.sessionBus()
    connection.registerService('org.example.CarExample')
    connection.registerObject('/Car', car)

    sys.exit(app.exec_())

 

 


思考

  1. 在 freedesktop 體系中,將interface至於Object下層。於是對於binding的設計,一般的(比如dbus-python)proxy可以獲得object的代理,而interface依舊是對象的namespace,故而在proxy下層。但Qt的命名似乎不太一樣——它所謂的 Interface 對象與代理很相似,而Qt概念中的代理,一般是通過 XML 轉換生成的。
  2. 一般的binding做到Proxy就可以了,而且一般Proxy確實夠用了;而Qt又設計了Adapter類,用於將DBus信號綁定QObject,其意義何在?
  3. 關於dbus消息傳遞時的載體是二進制流——那么它解碼后的內容是中間格式的XML嗎?亦或是給底層 lib 庫的C語言數據結構?
  4. 關於XML是為了實現跨平台、跨語言。但問題是,流程不應該是將代碼的結構,例如根據 QDBusConnection.ExportAllContents 的性質,將 @pyqtSlot 槽函數抽象為中間代碼,也就是XML,再將XML發送給daemon讀取形成總線服務或信號。但實際上這個過程卻顛倒過來了——如果你使用QDBusAdapter,你需要將xml文本寫入,或者通過 qdbusxml2cpp 再生成C++代碼。除了邏輯上的顛倒,更麻煩的是,沒有任何工具提供了靜態編譯驗證XML語法或語義的 debug 調試工具。一旦你不小心在<xml>中少寫了一個“/”結束符,程序並不報錯,只是服務項一直不正常~ 呃!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM