zoukankan      html  css  js  c++  java
  • QtDbus的API及示例

    Qt高级——QtDBus快速入门

    DBus for IPC in Qt

    1. Client :: Method Call

    1.1. 方法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();
    }
    

    1.2. 方法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();
    }
    

    1.3. 方法3: 从XML导入代理类

    1. 使用工具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>
      
    2. 使用工具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
      
      /*
       * 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();
      }
      

    2. Subscriber :: Signal Catching

    2.1. 方法1:BusConnection捕获信号

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

    2.2. 方法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)));
    

    3. Server/Publisher :: Register Service

    3.1. 方式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
    ",
                    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
    ",
                    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
    ", 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
    ", qPrintable(reply.value().variant().toString()));
            }
    
            if (iface->lastError().isValid())
                fprintf(stderr, "Call failed: %s
    ", 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.
    "
                    "To start it, run:
    "
                    "	eval `dbus-launch --auto-syntax`
    ");
            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();
    }
    

    3.2. 方式2:通过 XML 定义并转换为Adapter对象发布服务

    方法4: DBusAdapter 生成Adapter类的流程如下:

    1. 使用工具 qdbuscpp2xml从test.h生成XML文件

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

    2. 编辑com.scorpio.test.xml,选择需要发布的method,不需要发布的删除。

    3. 使用工具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">
      "
      "    <method name="maxValue">
      "
      "      <arg direction="out" type="i"/>
      "
      "    </method>
      "
      "    <method name="minValue">
      "
      "      <arg direction="out" type="i"/>
      "
      "    </method>
      "
      "  </interface>
      "
              "")
      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();
      }
      

    3.3. 其他:自启动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前,不必手动运行应用程序。

    4. QtDBus 在 PyQt5 中的应用

    常用类即其方法,请参考脑图

    4.1. 定义服务类

    这里提供一个实例代码(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_())
    

    4.2. 结合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">
    '
                '    <method name="turn_left">
    '
                '      <arg direction="in" type="i"/>
    '
                '    </method>
    '
                '    <method name="turn_right">
    '
                '      <arg name="degree" direction="in" type="i"/>
    '
                '      <arg name="any" direction="out" type="i"/>
    '
                '    </method>
    '
                '    <method name="turn_back"/>
    '
                '  </interface>
    '
                '')
    
        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_())
    

    5. 思考

    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 调试工具。一旦你不小心在中少写了一个“/”结束符,程序并不报错,只是服务项一直不正常~ 呃!

  • 相关阅读:
    BZOJ3498PA2009 Cakes——三元环
    黑科技之三元环讲解
    BZOJ4317Atm的树&BZOJ2051A Problem For Fun&BZOJ2117[2010国家集训队]Crash的旅游计划——二分答案+动态点分治(点分树套线段树/点分树+vector)
    BZOJ2463[中山市选2009]谁能赢呢?——博弈论
    BZOJ2275[Coci2010]HRPA——斐波那契博弈
    BZOJ2281[Sdoi2011]黑白棋&BZOJ4550小奇的博弈——DP+nimk游戏
    BZOJ3435[Wc2014]紫荆花之恋——动态点分治(替罪羊式点分树套替罪羊树)
    Trie树学习总结
    kmp学习小结
    Hash学习小结
  • 原文地址:https://www.cnblogs.com/brt2/p/13586094.html
Copyright © 2011-2022 走看看