Qtの基礎 - D-Bus

提供:MochiuWiki - SUSE, Electronic Circuit, PCB
2024年7月28日 (日) 21:23時点におけるWiki (トーク | 投稿記録)による版 (→‎Qtプロジェクト)
ナビゲーションに移動 検索に移動

概要

D-Busは、オープンソースのプロセス間通信(IPC:Inter Process Communication)機構であり、freedesktop.orgプロジェクトの一部である。
IPCとは、1台のコンピュータ上で動作する複数のプログラムの間で情報を交換するシステムのことである。

IPCには、パイプ、名前付きパイプ、シグナル、共有メモリ、Unixソケット、ループバックソケット等がある。
D-Busもリンク層はUnixソケットで動作しているが、手順とフォーマット(プレゼンテーション層)が既定されていることが、「生の」Unixソケットとは異なる。

開発当初はGNOME等のGUIの制御を目的としていが、今日では、GUIに限らず幅広いソフトウェアで使用されており、
デスクトップの通知、メディアプレーヤー制御、XDGポータル等、多くのfreedesktop.org標準がD-Busをベースに構築されている。

IPCとは、あるプロセスから別のプロセスへ情報を取得する方法を説明するために使用することができる。
これは、データの交換、メソッドの呼び出し、イベントのリスニング等がある。

  • デスクトップにおけるIPCの使用例
    • スクリプト(ユーザが共通の環境でスクリプトを実行して、実行中の様々なソフトウェアと対話または制御する)
    • 集中型サービスへのアクセスの提供
    • 協調型ソフトウェアの複数のインスタンス間の調整


  • D-Busの使用例
    • freedesktop.orgのnotification仕様
      これは、ソフトウェアは通知を中央サーバ(Plasma等)に送信して、中央サーバは通知を表示して、通知が閉じられたりアクションが実行されたりといったイベントを送り返すものである。


  • IPCの他の使用例
    • ユニークなソフトウェアのサポート
      これは、ソフトウェアの起動時に、まず、同じソフトウェアの他の実行中のインスタンスを確認して、
      もし存在すれば、IPCを介して実行中のインスタンスにメッセージを送信して、自分自身を表示して終了させる。


D-Busは、言語やツールキットに囚われないため、あらゆるプロバイダのソフトウェアやサービスが相互作用することができる。
デーモン(軽量サービスプロバイダ)とそれを利用したいソフトウェアが、必要なサービス以上のことを知らなくても通信できるようにするためによく利用される。

また、Qtは、D-Busと対話するためのクラスとツールのセットを提供している。

D-Busの詳細を知りたい場合は、設定 - D-Busのページを参照すること。


QtとD-Bus

Qt D-Busライブラリ

Qt D-Busライブラリは、D-Busプロトコルを使用してプロセス間通信を行うUnix向けライブラリであり、D-Busの基本APIをカプセル化して実装したものである。
これは、Qtのシグナル・スロット機構で拡張されたインターフェイスを提供する。

Qt D-Busライブラリを使用するには、QtDBusをインクルードする必要がある。

 #include <QtDBus>


Qtプロジェクトファイルを使用する場合は、変数QTdbusオプションを追加する必要がある。

QT += dbus


QtとD-Busのデータ型

QtとD-Busは、QDBusArgumentクラスを通して、ネイティブな型をサポートしている。
また、QDBusArgumentクラスは、ネイティブな型の他に非ネイティブ型であるQStringListクラスとQByteArrayクラスもサポートする。

Qtのデータ型 D-Busのデータ型
uchar BYTE
bool BOOLEAN
short INT16
ushort UINT16
int INT32
uint UINT32
qlonglong INT64
qulonglong UNIT64
double DOUBLE
QString STRING
QDBusVariant VARIANT
QDBusObjectPath OBJECT_PATH
QDBusSignature SIGNATURE


  • コンポジットタイプ
    D-Busでは、ネイティブの型を集約した3つの複合型を規定しており、ARRAYSTRUCTマップ / ディクショナリが存在する。
    ARRAYは、0個以上の同一の要素からなる集合体である。
    STRUCTは、異なる型の固定数の要素で構成されるコレクションである。
    マップ / ディクショナリは、要素のペアのARRAYである。マップは0個以上の要素を持つことができる。


  • 拡張された型システム
    Qt D-Busライブラリにおいて、カスタムデータ型を使用する場合、任意のクラスにQ_DECLARE_METATYPE()をQtメタタイプとして宣言して、
    qDBusRegisterMetaType関数で登録する必要がある。
    ストリーム演算子は、登録システムによって自動的に検出されます。

    Qt D-Busライブラリは、Qtコンテナクラスがストリーム演算子関数を実装せずに、QMapQListのような配列やマップを使用するためのテンプレート特殊化を提供している。
    それ以外の型では、フローオペレータが実装を表示する必要がある。


QDBusMessageクラス

QDBusMessageクラスは、D-Busが送信または受信するメッセージを表す。

QDBusMessageクラスは、バス上の4種類のメッセージタイプから1種類を選択する。
4種類のメッセージタイプを、以下に示す。

  • メソッド呼び出し
  • メソッドの戻り値
  • シグナルの送信
  • エラーコード


メッセージは、createErrorメソッド、createMethodCallメソッド、createSignalメソッドを使用して作成することができる。(全てstaticメソッド)
QDBusConnectionクラスのsendメソッド(staticメソッド)を使用して、メッセージを送信する。

QDBusConnectionクラス

QDBusConnectionクラスは、D-Busへの接続を表しており、D-Busセッションの出発点となる。

QDBusConnectionクラスを通じてD-Busへ接続することにより、
リモートオブジェクトやインターフェースへのアクセス、リモートシグナルをローカルスロット関数へ接続、オブジェクトの登録等を行うことができる。

D-Bus接続は、connectToBusメソッドを通して行われて、バスサーバへの接続を作成および初期化した後、D-Bus接続名を接続に関連付ける。

切断する場合は、disconnectFromBusメソッドを使用する。
D-Bus接続を切断した後、connectToBusメソッドは接続を再構築しないため、新しいQDBusConnectionクラスのインスタンスを生成する必要がある。

最もよく使用される2種類のバスタイプとして、sessionBusメソッドとsystemBusメソッドがあり、それぞれセッションバスとシステムバスへの接続を作成する。
これは、最初に使われるときに開かれ、QCoreApplicationクラスのデストラクタが呼ばれる時に切断される。

また、D-Busは、バスサービスを使用しないポイントツーポイント通信をサポートしており、2つのソフトウェアが直接通信することにより、メッセージを送受信することができる。
これは、connectToBusメソッドにアドレスを渡すことで可能となる。

  • QDBusConnection connectToBus(BusType type, const QString &name)
    第1引数に指定した接続を開いて、第2引数で指定した接続名と関連付ける。
    この接続に関連付けられたQDBusConnectionクラスのインスタンスを返す。


  • QDBusConnection connectToBus(const QString &address, const QString &name)
    第1引数で指定したプライベートバスを開いて、第2引数で指定した接続名と関連付ける。
    この接続に関連付けられたQDBusConnectionクラスのインスタンスを返す。


  • QDBusConnection connectToPeer(const QString &address, const QString &name)
    第1引数で指定したポイントツーポイント接続を開いて、接続名を関連付ける。
    この接続に関連付けられたQDBusConnectionクラスのインスタンスを返す。


  • void disconnectFromBus(const QString &name)
    引数で指定した名前のバス接続を閉じる。


  • void disconnectFromPeer(const QString &name)
    引数で指定した名前のピア接続を閉じる。


  • QByteArray localMachineId()
    D-Busシステムが知っているローカルIDを返します。


  • QDBusConnection sender()
    シグナルを送信した接続を返す。


  • QDBusConnection sessionBus()
    セッションバスに対して開いたQDBusConnectionクラスのインスタンスを返す。


  • QDBusConnection systemBus()
    システムバスに対して開いたQDBusConnectionクラスのインスタンスを返す。


  • QDBusPendingCall asyncCall(const QDBusMessage &message, int timeout = -1) const
    メッセージは、この接続を通じてメッセージを送信して、直ちにリターンする。
    このメソッドは、メソッド呼び出しのみをサポートしており、レスポンスを追跡するために使用されるQDBusPendingCallクラスのインスタンスを返す。


  • QDBusMessage call(const QDBusMessage & message, QDBus::CallMode mode = QDBus::Block, int timeout = -1 ) const
    メッセージは、この接続を通じて送信・ブロックして、応答を待つ。


  • bool registerObject(const QString &path, QObject *object, RegisterOptions options = ExportAdaptors)
    第2引数で指定した現在のクラスのインスタンスを、第1引数に指定したQ-DBusオブジェクト名に登録する。
    第3引数には、D-Busに公開されるオブジェクトの数を指定する。
    登録に成功した場合は、trueを返す。


  • bool registerService(const QString &serviceName)
    第1引数に指定したバス名(D-Busサービス名)に登録する。
    登録に成功した場合はtrue、指定したバス名(D-Busサービス名)が他のソフトウェアで既に登録されている場合は登録に失敗してfalseを返す。


qdbuscpp2xmlコマンド

qdbuscpp2xmlコマンドは、QObjectクラスを継承したクラスのヘッダファイル等をパースして、D-Busインターフェースファイルを生成する。
スロットの引数がconstで宣言されている場合は入力、非constの場合は出力とみなされることがある。

qdbuscpp2xml <入力オプション> <QObjectクラスを継承したクラスを定義したヘッダファイル> <出力オプション> <生成するD-Busインターフェースファイル名>
例. qdbuscpp2xml -A SampleHelper.h -o org.qt.sample.xml


qdbuscpp2xmlコマンドのオプションを、以下に示す。

  • -p または -s または -m
    スクリプト化されたアトリビュート(-pオプション)、シグナル(-sオプション)、メソッド(スロット関数)(-mオプション)のみがパースされる。
  • -P または -S または -M
    全てのアトリビュート(-Pオプション)、シグナル(-Sオプション)、メソッド(スロット関数)(-Mオプション)を解析する。
  • -a
    スクリプト化された内容を全て出力する。(-psmオプションと等価)
  • -A
    全てのコンテンツを出力する。(-PSMオプションと等価)
  • -o <生成するD-Busインターフェースファイル名>
    D-Busインターフェースファイルを生成する。


以下の例では、C++クラスからD-Busインターフェイスファイル (XMLファイル) を生成している。

まず、インターフェイスを定義したC++クラスを作成する。

 // ExampleObject.h
 
 #include <QObject>
 #include <QString>
 
 class ExampleObject : public QObject
 {
    Q_OBJECT
    Q_CLASSINFO("D-Bus Interface", "com.example.ExampleInterface")
 
 public slots:
    QString exampleMethod(const QString &input)
    {
        return "Hello, " + input;
    }
 };


次に、qdbuscpp2xmlコマンドを実行して、D-Busインターフェースファイル (XMLファイル) を生成する。
以下の例では、ExampleObject.hファイルからインターフェイスを読み取り、ExampleInterface.xmlファイルを生成している。

qdbuscpp2xml -m -s ExampleObject.h -o ExampleInterface.xml


生成されたD-Busインターフェースファイル (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.example.ExampleInterface">
     <method name="exampleMethod">
       <arg direction="in" type="s" name="input"/>
       <arg direction="out" type="s" name="output"/>
     </method>
   </interface>
 </node>


qdbusxml2cppコマンド

qdbusxml2cppコマンドは、D-Busインターフェースファイルの定義に従い、C++のアダプターコードを生成する。
これは、qdbusabstractadapterQDBusAbstractInterfaceを継承したプロセス通信サーバとクライアントの実装コードを自動生成する。

# mocファイルはインクルードしない場合
qdbusxml2cpp -a <生成するcppファイル名とヘッダファイル名> -c <自動生成するヘルパークラス名 (親クラス)> -i <対象となるクラスを記述しているヘッダファイル> -l <対象となるクラス名> <D-Busインターフェースファイルのパス>
例. qdbusxml2cpp -a SamplesAdaptor -c SamplesAdaptor -i SampleHelper.h -l SampleHelper org.qt.policykit.examples.xml

# mocファイルもインクルードする場合
qdbusxml2cpp -m -a <生成するcppファイル名とヘッダファイル名> -c <自動生成するヘルパークラス名 (親クラス)> -i <対象となるクラスを記述しているヘッダファイル> -l <対象となるクラス名> <D-Busインターフェースファイルのパス>
例. qdbusxml2cpp -m -a SamplesAdaptor -c SamplesAdaptor -i SampleHelper.h -l SampleHelper org.qt.policykit.examples.xml

# 各クラスに個別のインターフェースを宣言している場合は、各インターフェースごとにアダプターを生成する
qdbusxml2cpp -m -a <生成するcppファイル名とヘッダファイル名> -c <自動生成するヘルパークラス名 (親クラス)> -i <対象となるクラスを記述しているヘッダファイル> -l <対象となるクラス名> <D-Busインターフェースファイルのパス> <D-Busインターフェース名>
例. qdbusxml2cpp -m -a SamplesAdaptor -c SamplesAdaptor -i SampleHelper.h -l SampleHelper org.qt.policykit.examples.xml org.qt.policykit.examples2


qdbusxml2cppコマンドのオプションを、以下に示す。

  • -a <アダプターソースコードおよびヘッダのファイル名> または -A <アダプターソースコードおよびヘッダのファイル名>
    指定された名前でアダプターソースコードおよびヘッダを生成する。
  • -c <アダプターソースコードで使用するクラス名> または -C <アダプターソースコードで使用するクラス名>
    アダプターソースコードで使用するクラス名を指定する。
  • -i <対象となるクラスを記述しているヘッダファイル名> または -I <対象となるクラスを記述しているヘッダファイル名>
    アダプターソースコードに、#include <対象となるクラスを記述しているヘッダファイル>を追加する。
  • -l <対象となるクラス名> または -L <対象となるクラス名>
    アダプタソースコードを生成する時に使用するクラス名(親クラス)を指定する。
  • -m
    アダプターソースコードに、#include "<アダプターソースコード名.moc>"ステートメントを追加する。
  • -N
    名前空間を使用しない。
  • -p <アダプターソースコードのファイル名> または -P <アダプターソースコードのファイル名>
    アダプターソースコードのファイルへのプロキシコードを生成する。


サンプルコード

以下の例では、Qtにおいて、D-Busを使用したメッセージの送受信を行っている。

  1. セッションバスへの接続を取得する。
  2. セッションバスへのD-Busインターフェースを作成する。
  3. D-Busインターフェイス名でのサービス登録する。
  4. バス上に誰が存在しているかを確認する。
  5. pingリクエストを受信するために登録する。
  6. Pingメッセージの送信する。
  7. 受信したPingメッセージの表示する。


受信したPingメッセージの表示以外は、doItメソッドで行う。
Pingメッセージは、pingReceivedメソッドで処理される。

プログラムを実行すると、セッションバス上に既に多くのサービスが表示されるが、"-- end --"の付近にPingリクエストに応答していることがわかる。

 #include <QDBusConnection>
 #include <QDBusConnectionInterface>
 #include <QTimer>
 #include "MyObject.h"
 
 MyObject::MyObject(QObject *parent) : QObject(parent)
 {
    QTimer::singleShot(10, this, &MyObject::doIt);
 }
 
 void MyObject::doIt()
 {
    /// [1] セッションバスへの接続を取得
    QDBusConnection bus = QDBusConnection::sessionBus();
 
    /// [2] セッションバスへD-Busインターフェースを作成
    QDBusConnectionInterface *busIF = bus.interface();
 
    /// [3] D-Busインターフェイス名でのサービス登録
    QString ifName = "com.packt.bigproject";
    busIF->registerService(ifName,
                           QDBusConnectionInterface::ReplaceExistingService,
                           QDBusConnectionInterface::AllowReplacement);
 
    /// [4] バス上に誰が存在しているかを確認
    QDBusReply<QStringList> serviceNames = busIF->registeredServiceNames();
    qDebug() << bus.name() << "knows the following Services:" << serviceNames.value();
 
    /// [5] pingリクエストを受信するために登録 (QObject::connectメソッドと似ている)
    QString service = "";
    QString path = "";
    QString name = "ping";
    bus.connect(service, path, ifName, name, this, SLOT(pingReceived(QString)));
 
    /// [6] Pingメッセージを送信
    QDBusMessage msg = QDBusMessage::createSignal("/", ifName, name);
    msg << "Hello World!";
    bus.send(msg);
 
    /// 5秒以内にもう1度行う
    QTimer::singleShot(5000, this, &MyObject::doIt);
 }
 
 void MyObject::pingReceived(QString msg)
 {
    /// [8] 受信したPingメッセージの表示
    qDebug() << __FUNCTION__ << "Ping:" << msg;
 }


サンプルコードの詳細を知りたい場合は、以下に示すGithubを参照すること。
https://github.com/PacktPublishing/Hands-On-Embedded-Programming-with-Qt/blob/master/Chapter10/DBusBruteForce/MyObject.cpp


Qtプロジェクト

Qtプロジェクトにおいて、Qt Creator付属のqdbusxml2cppコマンドを実行することにより、D-Busインターフェースのアダプタクラスを生成する。
生成されるファイルは、D-Busインタフェースファイル(XML形式)に対するアダプタを実装したC++のソースコードファイルとヘッダファイルである。

# mocファイルはインクルードしない場合
qdbusxml2cpp -a <生成するcppファイル名とヘッダファイル名> -c <自動生成するヘルパークラス名 (親クラス)> -i <対象となるクラスを記述しているヘッダファイル> -l <対象となるクラス名> <D-Busインターフェースファイルのパス>
例. qdbusxml2cpp -a SamplesAdaptor -c SamplesAdaptor -i SampleHelper.h -l SampleHelper org.dbus.interface.examples.xml

# mocファイルもインクルードする場合
qdbusxml2cpp -m -a <生成するcppファイル名とヘッダファイル名> -c <自動生成するヘルパークラス名 (親クラス)> -i <対象となるクラスを記述しているヘッダファイル> -l <対象となるクラス名> <D-Busインターフェースファイルのパス>
例. qdbusxml2cpp -m -a SamplesAdaptor -c SamplesAdaptor -i SampleHelper.h -l SampleHelper org.dbus.interface.examples.xml


生成されたファイルをQtプロジェクトに指定およびインクルードする。

または、Qtプロジェクトファイルにおいて、qdbusxml2cppコマンドを自動実行する設定を記述してもよい。

system(qdbusxml2cpp -a <生成するcppファイル名とヘッダファイル名> -c <自動生成するヘルパークラス名 (親クラス)> -i <対象となるクラスを記述しているヘッダファイル> -l <対象となるクラス名> <D-Busインターフェースファイルのパス>)



CMakeプロジェクト

CMakeプロジェクトにおいて、qt_add_dbus_adaptorコマンドを指定することにより、D-Busインターフェースのアダプタクラスを生成することができる。
qt_add_dbus_adaptorコマンドは、Qt D-Bus XMLコンパイラ (qdbusxml2cpp) のアダプタモードでの呼び出しを設定する。

第2引数で指定したD-Busインタフェースファイル(XML形式)に対するアダプタを実装したC++のソースコードファイルとヘッダファイルを生成する。
生成されたファイルのパスが第1引数に追加される。

第3引数には、D-Busインターフェースに基づく親クラスのヘッダファイル名を指定する。
生成されるアダプタを実装したソースコードには、#include "<第3引数で指定したヘッダファイル名>"としてインクルードされる。

第4引数には、第3引数のクラス名(D-Busインターフェースに基づく親クラス名)を指定する。(省略可能)
第5引数には、生成するヘッダファイル名(拡張子.hは不要)を指定する。(省略可能)
第6引数には、生成するアダプタのクラス名を指定する。(省略可能)

第4引数から第6引数までを省略する場合、親クラス名、ヘッダファイル名、クラス名は、第3引数の指定値から自動的に生成される。

 qt_add_dbus_adaptor(
   <任意の変数名>             # 生成されるソースファイル名を指定  例: SampleAdaptor.cpp  SampleAdaptor.h
   <D-Busインタフェースファイル>   # XML形式
   <親クラスのヘッダファイル名>
   <第3引数のクラス名>         # 省略可能
   <生成するヘッダファイル名>     # 省略可能
                            # 拡張子.hは不要
   <生成するアダプタのクラス名>   # 省略可能
 )