Qtの基礎 - D-Bus

提供:MochiuWiki : SUSE, EC, PCB
ナビゲーションに移動 検索に移動

概要

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プロジェクト (.pro) / CMakeLists.txt

  • Qtプロジェクト (.pro) を使用する場合
 QT += dbus


  • CMakeLists.txtを使用する場合
 # QtDBusライブラリの検索 (Qt6を検索して、無ければQt5を使用)
 find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS DBus)
 
 # QtDBusライブラリを検索
 find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS DBus)
 
 # QtDBusライブラリとのリンク
 target_link_libraries(<ターゲット名> PRIVATE
    Qt${QT_VERSION_MAJOR}::DBus
 )



使用例

pingの送受信

以下の例では、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

Firewalldの利用

以下の例では、Firewalldにおいて、以下に示すようなコマンドと同等のものをD-Busを使用して実行している。

sudo firewall-cmd --permanent --zone=<ゾーン名> --add-port=<ポート番号>/tcp
sudo firewall-cmd --reload


以下に示すサンプルコードを実行するには、適切な権限 (root権限) が必要となることに注意する。

 #include <QDBusConnection>
 #include <QDBusInterface>
 #include <QDBusReply>
 
 // 開放するポートを指定する。
 bool addPort(QDBusInterface &interface, const QString &zone, QString &port, const QString &protocol)
 {
    QDBusReply<void> reply = interface.call("addPort", zone, port, protocol, 0);
    return reply.isValid();
 }
 
 // 変更を永続化する
 bool makePermament(QDBusInterface &interface)
 {
    QDBusReply<void> reply = interface.call("runtimeToPermanent");
    return reply.isValid();
 }
 
 // Firewalldを再読み込みする
 bool reloadFirewall(QDBusInterface &interface)
 {
    QDBusReply<void> reply = interface.call("reload");
    return reply.isValid();
 }
 
 int main()
 {
    // ...略
 
    // Firewalldの (ポート開放向け) D-Busインターフェースに接続する
    QDBusConnection bus = QDBusConnection::systemBus();
    QDBusInterface interface("org.fedoraproject.FirewallD1",
                             "/org/fedoraproject/FirewallD1",
                             "org.fedoraproject.FirewallD1.zone",
                             bus);
 
    // 複数のゾーンに対して操作を行うことも可能
    QStringList zones = {"public", "internal", "work"};
    for (const auto& zone : zones) {
       if (addPort(interface, zone, "80", "tcp")) {
          qDebug() << "Port 80 added to" << zone << "zone successfully";
       }
       else {
          qDebug() << "Failed to add port 80 to" << zone << "zone";
          return -1;
       }
    }
 
    // 変更を恒久的に設定する
    QDBusInterface ifReload("org.fedoraproject.FirewallD1",
                            "/org/fedoraproject/FirewallD1",
                            "org.fedoraproject.FirewallD1",
                            bus);
    if (makePermament(ifReload)) {
       qDebug() << "Changes made permanent";
       return -1;
    }
 
    if (reloadFirewall(ifReload)) {
       qDebug() << "Firewall reloaded";
       return -1;
    }
 
    // ...略
 
    return 0;
 }


構造体の送信

以下の例では、D-Busを使用して構造体を送信している。

 // DBusSender.hファイル
 
 #include <QCoreApplication>
 #include <QDBusConnection>
 #include <QDBusMessage>
 #include <QDebug>
 
 struct Hoge {
    QString     str;
    QStringList list;
    int         integer;
    double      fpoint;
 };
 
 // D-Bus向けマーシャリング関数
 QDBusArgument &operator<<(QDBusArgument &argument, const Hoge &hoge)
 {
    argument.beginStructure();
    argument << hoge.str << hoge.list << hoge.integer << hoge.fpoint;
    argument.endStructure();
    return argument;
 }
 
 // D-Bus向けマーシャリング関数
 const QDBusArgument &operator>>(const QDBusArgument &argument, Hoge &hoge)
 {
    argument.beginStructure();
    argument >> hoge.str >> hoge.list >> hoge.integer >> hoge.fpoint;
    argument.endStructure();
    return argument;
 }
 
 class DBusSender : public QObject
 {
    Q_OBJECT
 
 public:
    DBusSender(QObject *parent = nullptr) : QObject(parent) {}
 
    void sendStructure(const Hoge &hoge) {
       QDBusMessage message = QDBusMessage::createMethodCall(
            "org.example.hoge",   // D-Busサービス名
            "/org/example/hoge",  // D-Busオブジェクト名
            "org.example.hoge",   // D-Busインターフェース名
            "sendstructure"       // D-Busインターフェースメソッド名
       );
 
       QVariant variant;
       variant.setValue(hoge);
       message << variant;
 
       QDBusConnection::sessionBus().send(message);
    }
 };


 // main.cppファイル
 
 #include "DBusSender.h"
 
 int main(int argc, char *argv[])
 {
    QCoreApplication a(argc, argv);
 
    // 送信する構造体の型をD-Busシステムに登録
    qDBusRegisterMetaType<Hoge>();
 
    DBusSender sender;
 
    // 送信データの作成
    Hoge hoge;
    hoge.str     = "Hello, D-Bus!";
    hoge.list    = QStringList{"item1", "item2", "item3"};
    hoge.integer = 100;
    hoge.fpoint  = 3.14f;
 
    // データを送信
    sender.sendStructure(hoge);
 
    qDebug() << "Structure sent via D-Bus";
 
    return a.exec();
 }


qDBusRegisterMetaType<構造体>()は、QtのD-Busシステムに対して、カスタム型を登録するための関数呼び出しである。
これは、カスタム型をD-Bus経由で送信する際に必須の操作である。

  • 型の登録
    この関数は、QtのD-Busシステムに構造体を知らせるためのものである。
    D-Busは標準的なデータ型 (整数、文字列等) を扱うことができるが、カスタム型については明示的に登録する必要がある。

  • シリアライズとデシリアライズ
    この登録により、Qtはカスタム型をどのようにシリアライズ (バイトストリームに変換) して、デシリアライズ (バイトストリームから元の型に戻す) すべきかを理解する。
    これは、D-Busを通じてデータを送受信する時に必要となる。

  • マーシャリング関数の使用
    登録プロセスは、operator<<operator>>関数を使用する。
    これらの関数が、カスタム型のマーシャリング (データの変換) 方法を定義している。

  • 型の安全性
    この登録により、D-Bus経由でカスタム型のオブジェクトを安全に送受信できるようになる。
    Qtは、送信時にこの型をシリアライズして、受信時に正しくデシリアライズすることができる。

  • メタオブジェクトシステムとの統合
    この関数呼び出しは、カスタム型をQtのメタオブジェクトシステムに統合する。
    これにより、Qtの様々な機能 (シグナル/スロットシステム等) でカスタム型が使用できるようになる。

  • 実行時の型情報
    この登録により、D-Busシステムは実行時にカスタム型の情報を持つことができ、適切なデータ変換を行うことができる。


したがって、qDBusRegisterMetaType<カスタム型>関数を呼び出すことにより、カスタム型をD-Busシステムで使用可能にして、データを送受信できるようにしている。

構造体の受信

 // DBusReceiver.hファイル
 
 #include <QCoreApplication>
 #include <QDBusConnection>
 #include <QDBusMessage>
 #include <QDBusError>
 #include <QDebug>
 
 struct Hoge {
    QString str;
    QStringList list;
    int integer;
    double fpoint;
 };
 
 Q_DECLARE_METATYPE(Hoge)
 
 // D-Bus向けマーシャリング関数
 QDBusArgument &operator<<(QDBusArgument &argument, const Hoge &hoge) {
    argument.beginStructure();
    argument << hoge.str << hoge.list << hoge.integer << hoge.fpoint;
    argument.endStructure();
    return argument;
 }
 
 // D-Bus向けマーシャリング関数
 const QDBusArgument &operator>>(const QDBusArgument &argument, Hoge &hoge) {
    argument.beginStructure();
    argument >> hoge.str >> hoge.list >> hoge.integer >> hoge.fpoint;
    argument.endStructure();
    return argument;
 }
 
 class DBusReceiver : public QObject {
    Q_OBJECT
 
    // Q_CLASSINFOマクロを使用して、D-Busインターフェース名を指定
    Q_CLASSINFO("D-Bus Interface", "org.example.hoge")
 
 public:
    DBusReceiver(QObject *parent = nullptr) : QObject(parent) {}
 
 public slots:
    bool sendstructure(const Hoge &hoge)
    {
       try {
          qDebug() << "受信した構造体:";
          qDebug() << "str: " << hoge.str;
          qDebug() << "list: " << hoge.list;
          qDebug() << "integer: " << hoge.integer;
          qDebug() << "fpoint: " << hoge.fpoint;
 
          return true;
       }
       catch (const std::exception &e) {
          qCritical() << "Error processing received structure: " << e.what();
          return false;
       }
    }
 };


 // main.cppファイル
 
 #include "DBusReceiver.h"
 
 bool setupDBusConnection(DBusReceiver &receiver)
 {
    QDBusConnection connection = QDBusConnection::sessionBus();
 
    if (!connection.isConnected()) {
        qCritical() << "Cannot connect to the D-Bus session bus:" << connection.lastError().message();
        return false;
    }
 
    if (!connection.registerService("org.example.hoge")) {
        qCritical() << "Failed to register service:" << connection.lastError().message();
        return false;
    }
 
    if (!connection.registerObject("/org/example/hoge", &receiver, QDBusConnection::ExportAllSlots)) {
        qCritical() << "Failed to register object:" << connection.lastError().message();
        return false;
    }
 
    return true;
 }
 
 int main(int argc, char *argv[])
 {
    QCoreApplication a(argc, argv);
 
    // カスタム型をD-Busシステムに登録
    qRegisterMetaType<Hoge>("Hoge");
    qDBusRegisterMetaType<Hoge>();
 
    DBusReceiver receiver;
 
    if (!setupDBusConnection(receiver)) {
        return -1;
    }
 
    qDebug() << "D-Bus server is running. Waiting for incoming messages...";
 
    return a.exec();
 }


Q_DECLARE_METATYPE(カスタム型)は、Qtのメタオブジェクトシステムにカスタム型を登録するためのマクロである。

  • 型の登録
    この宣言により、カスタム型がQtのメタオブジェクトシステムに認識される。
    これにより、Qtの様々な機能 (シグナル/スロットシステム、プロパティシステム等) でカスタム型を使用できるようになる。

  • QVariantとの互換性
    カスタム型をQVariantオブジェクトに格納、および、QVariant型から取り出すことが可能になる。

  • シリアライゼーション
    Qtの機能を使用してカスタム型のオブジェクトをシリアライズ (バイナリデータに変換)、デシリアライズ (バイナリデータから元のオブジェクトに戻す) することができるようになる。

  • 型の安全性
    コンパイル時の型チェックが可能になり、カスタム型を使用する時の型の安全性が向上する。

  • qRegisterMetaTypeとの連携
    Q_DECLARE_METATYPEqRegisterMetaTypeを併用することにより、
    Qtのスレッド間通信やイベントシステムでもカスタム型を安全に使用できるようになる。

  • D-Busとの連携
    D-Busシステムでカスタム型を使用する場合、この宣言により型情報が適切に処理される。

  • 動的プロパティ
    Qtの動的プロパティシステムでカスタム型を使用することが可能になる。


具体的な使用例を以下に示す。

 // シグナル / スロットでの使用
 signals:
    void hogeChanged(const Hoge &newHoge);
 
 // QVariant型での使用
 Hoge myHoge;
 QVariant variant = QVariant::fromValue(myHoge);
 
 // D-Busでの使用
 QDBusMessage message;
 message << QVariant::fromValue(myHoge);


※注意
Q_DECLARE_METATYPEはヘッダファイル内で使用して、
対応するqRegisterMetaType関数呼び出しは、一般的に、main関数内や型を使用する前に1度だけ行う必要がある。

このマクロを使用することにより、カスタム型がQtのシステムにシームレスに統合されて、D-Bus通信を含む様々なQtの機能で安全かつ効率的に使用できるようになる。


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は不要
   <生成するアダプタのクラス名>   # 省略可能
 )