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

/* * 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

/* * 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() { }
調用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

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