「Qtの基礎 - D-Bus」の版間の差分

提供:MochiuWiki - SUSE, Electronic Circuit, PCB
ナビゲーションに移動 検索に移動
(ページの作成:「== 概要 == D-Busは、オープンソースのプロセス間通信(IPC)機構であり、freedesktop.orgプロジェクトの一部である。<br> 幅広いソフトウェアで使用されており、デスクトップの通知、メディアプレーヤー制御、XDGポータル等、多くのfreedesktop.org標準がD-Busをベースに構築されている。<br> <br> IPCとは、あるプロセスから別のプロセスへ情報を取得する方法を説…」)
 
 
(同じ利用者による、間の37版が非表示)
1行目: 1行目:
== 概要 ==
== 概要 ==
D-Busは、オープンソースのプロセス間通信(IPC)機構であり、freedesktop.orgプロジェクトの一部である。<br>
D-Busは、オープンソースのプロセス間通信(IPC:Inter Process Communication)機構であり、freedesktop.orgプロジェクトの一部である。<br>
幅広いソフトウェアで使用されており、デスクトップの通知、メディアプレーヤー制御、XDGポータル等、多くのfreedesktop.org標準がD-Busをベースに構築されている。<br>
IPCとは、1台のコンピュータ上で動作する複数のプログラムの間で情報を交換するシステムのことである。<br>
<br>
IPCには、パイプ、名前付きパイプ、シグナル、共有メモリ、Unixソケット、ループバックソケット等がある。<br>
D-Busもリンク層はUnixソケットで動作しているが、手順とフォーマット(プレゼンテーション層)が既定されていることが、「生の」Unixソケットとは異なる。<br>
<br>
開発当初はGNOME等のGUIの制御を目的としていが、今日では、GUIに限らず幅広いソフトウェアで使用されており、<br>
デスクトップの通知、メディアプレーヤー制御、XDGポータル等、多くのfreedesktop.org標準がD-Busをベースに構築されている。<br>
<br>
<br>
IPCとは、あるプロセスから別のプロセスへ情報を取得する方法を説明するために使用することができる。<br>
IPCとは、あるプロセスから別のプロセスへ情報を取得する方法を説明するために使用することができる。<br>
21行目: 27行目:
<br>
<br>
D-Busは、言語やツールキットに囚われないため、あらゆるプロバイダのソフトウェアやサービスが相互作用することができる。<br>
D-Busは、言語やツールキットに囚われないため、あらゆるプロバイダのソフトウェアやサービスが相互作用することができる。<br>
デーモン(軽量サービスプロバイダ)とそれを利用したいソフトウェアが、必要なサービス以上のことを知らなくても通信できるようにするためによく利用される。<br>
<br>
また、Qtは、D-Busと対話するためのクラスとツールのセットを提供している。<br>
<br>
<br>
Qtは、D-Busと対話するためのクラスとツールのセットを提供しており、ここでは、D-Busのハイレベルなコンセプト、および、QtやKDEソフトウェアでの実装を記載する。<br>
D-Busの詳細を知りたい場合は、[[設定 - D-Bus]]のページを参照すること。<br>
<br><br>
<br><br>


== バス ==
== QtとD-Bus ==
D-Busは、ソフトウェアが相互に通信するため、複数のメッセージバスを提供する。<br>
==== Qt D-Busライブラリ ====
Qt D-Busライブラリは、D-Busプロトコルを使用してプロセス間通信を行うUnix向けライブラリであり、D-Busの基本APIをカプセル化して実装したものである。<br>
これは、Qtのシグナル・スロット機構で拡張されたインターフェイスを提供する。<br>
<br>
Qt D-Busライブラリを使用するには、<code>QtDBus</code>をインクルードする必要がある。<br>
<syntaxhighlight lang="c++">
#include <QtDBus>
</syntaxhighlight>
<br>
Qtプロジェクトファイルを使用する場合は、変数<code>QT</code>に<code>dbus</code>オプションを追加する必要がある。<br>
QT += dbus
<br>
<br>
各バスには独自の接続機能があり、異なるカテゴリーのメッセージを分離することができる。<br>
==== QtとD-Busのデータ型 ====
QtとD-Busは、<code>QDBusArgument</code>クラスを通して、ネイティブな型をサポートしている。<br>
また、<code>QDBusArgument</code>クラスは、ネイティブな型の他に非ネイティブ型である<code>QStringList</code>クラスと<code>QByteArray</code>クラスもサポートする。<br>
<center>
{| class="wikitable" | style="background-color:#fefefe;"
|-
! style="background-color:#66CCFF;" | Qtのデータ型
! style="background-color:#66CCFF;" | 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
|}
</center>
<br>
<br>
あるバスで送信されたメッセージは他のバスからアクセスできないが、同じバスに接続されたソフトウェアは全て互いに通信することができる。<br>
* コンポジットタイプ
任意のバスに複数のソフトウェアを同時に接続することができ、また、1つのソフトウェアが複数のバスに同時に接続することも可能である。
*: D-Busでは、ネイティブの型を集約した3つの複合型を規定しており、<code>ARRAY</code>、<code>STRUCT</code>、<code>マップ / ディクショナリ</code>が存在する。
これにより、バスごとに異なるセキュリティポリシーを適用できると共に、グローバルメッセージとローカルメッセージの両方を効率的に共有することができる。<br>
*: <code>ARRAY</code>は、0個以上の同一の要素からなる集合体である。
*: <code>STRUCT</code>は、異なる型の固定数の要素で構成されるコレクションである。
*: <code>マップ / ディクショナリ</code>は、要素のペアの<code>ARRAY</code>である。マップは0個以上の要素を持つことができる。
<br>
<br>
D-Busは、2つの定義済みバス(<u>システムバス</u><u>セッションバス</u>)を予め用意されており、一般的なD-Busの使用方法をほぼカバーすることができる。<br>
* 拡張された型システム
* システムバス
*: Qt D-Busライブラリにおいて、カスタムデータ型を使用する場合、任意のクラスに<code>Q_DECLARE_METATYPE()</code>をQtメタタイプとして宣言して、
*: ハードウェア管理等のシステムグローバルサービスに使用される。
*: <code>qDBusRegisterMetaType</code>関数で登録する必要がある。
*: これはユーザー間で共有され、通常、厳格なセキュリティ・ポリシーが付属しています。
*: ストリーム演算子は、登録システムによって自動的に検出されます。
*: <br>
*: <br>
* セッションバス
*: Qt D-Busライブラリは、Qtコンテナクラスがストリーム演算子関数を実装せずに、<code>QMap</code>や<code>QList</code>のような配列やマップを使用するためのテンプレート特殊化を提供している。
*: 各デスクトップセッション(ログインしているユーザ等)には、セッションバスが存在する。
*: それ以外の型では、フローオペレータが実装を表示する必要がある。
*: これは、GUIソフトウェア等が最も頻繁に使用する傾向がある。
<br>
==== QDBusMessageクラス ====
<code>QDBusMessage</code>クラスは、D-Busが送信または受信するメッセージを表す。<br>
<br>
<code>QDBusMessage</code>クラスは、バス上の4種類のメッセージタイプから1種類を選択する。<br>
4種類のメッセージタイプを、以下に示す。<br>
* メソッド呼び出し
* メソッドの戻り値
* シグナルの送信
* エラーコード
<br>
<br>
さらに、ソフトウェアは、必要に応じて複数の独自のバスを作成することができる。<br>
メッセージは、<code>createError</code>メソッド、<code>createMethodCall</code>メソッド、<code>createSignal</code>メソッドを使用して作成することができる。(全て<code>static</code>メソッド)<br>
<br><br>
<code>QDBusConnection</code>クラスの<code>send</code>メソッド(<code>static</code>メソッド)を使用して、メッセージを送信する。<br>
 
<br>
== メッセージ ==
==== QDBusConnectionクラス ====
メッセージは、バスにおける通信の基本単位である。<br>
<code>QDBusConnection</code>クラスは、D-Busへの接続を表しており、D-Busセッションの出発点となる。<br>
<br>
<code>QDBusConnection</code>クラスを通じてD-Busへ接続することにより、<br>
リモートオブジェクトやインターフェースへのアクセス、リモートシグナルをローカルスロット関数へ接続、オブジェクトの登録等を行うことができる。<br>
<br>
D-Bus接続は、<code>connectToBus</code>メソッドを通して行われて、バスサーバへの接続を作成および初期化した後、D-Bus接続名を接続に関連付ける。<br>
<br>
切断する場合は、<code>disconnectFromBus</code>メソッドを使用する。<br>
D-Bus接続を切断した後、<code>connectToBus</code>メソッドは接続を再構築しないため、新しい<code>QDBusConnection</code>クラスのインスタンスを生成する必要がある。<br>
<br>
最もよく使用される2種類のバスタイプとして、<code>sessionBus</code>メソッドと<code>systemBus</code>メソッドがあり、それぞれセッションバスとシステムバスへの接続を作成する。<br>
これは、最初に使われるときに開かれ、<code>QCoreApplication</code>クラスのデストラクタが呼ばれる時に切断される。<br>
<br>
また、D-Busは、バスサービスを使用しないポイントツーポイント通信をサポートしており、2つのソフトウェアが直接通信することにより、メッセージを送受信することができる。<br>
これは、<code>connectToBus</code>メソッドにアドレスを渡すことで可能となる。<br>
<br>
* QDBusConnection connectToBus(BusType type, const QString &name)
*: 第1引数に指定した接続を開いて、第2引数で指定した接続名と関連付ける。
*: この接続に関連付けられた<code>QDBusConnection</code>クラスのインスタンスを返す。
<br>
* QDBusConnection connectToBus(const QString &address, const QString &name)
*: 第1引数で指定したプライベートバスを開いて、第2引数で指定した接続名と関連付ける。
*: この接続に関連付けられた<code>QDBusConnection</code>クラスのインスタンスを返す。
<br>
<br>
TCP/IPのパケットと同様に、バスでやり取りされる情報は、全てメッセージで行われる。<br>
* QDBusConnection connectToPeer(const QString &address, const QString &name)
しかし、ネットワークのパケットとは異なり、D-Busの各メッセージには、送受信されるデータ1式が含まれていることが保証されている。<br>
*: 第1引数で指定したポイントツーポイント接続を開いて、接続名を関連付ける。
*: この接続に関連付けられた<code>QDBusConnection</code>クラスのインスタンスを返す。
<br>
<br>
メッセージには、送信されるデータだけでなく、送信者と受信者が誰であるかも記録されており、適切なルーティングを可能にする。<br>
* void disconnectFromBus(const QString &name)
メッセージは、メソッドコール、シグナルエミッション、メソッドの戻り値であり、エラー情報も含むことがある。<br>
*: 引数で指定した名前のバス接続を閉じる。
<br><br>
 
== 名前空間とアドレス ==
複数のソフトウェアが同一のバス上に存在し、1つのソフトウェアが複数のオブジェクトを提供してメッセージを送信することができるため、<br>
特定の住宅やオフィスを一意に識別するのと同様に、任意のバス上の任意のオブジェクトを効果的かつ一意にアドレス指定する手段を持つことが必要である。<br>
<br>
<br>
インターフェース、サービス、オブジェクト名の3つの情報を組み合わせることにより、バス上の任意のオブジェクトに一意のアドレスを作成することができる。<br>
* void disconnectFromPeer(const QString &name)
*: 引数で指定した名前のピア接続を閉じる。
<br>
<br>
==== インターフェイス ====
* QByteArray localMachineId()
インターフェイスは、バス上に公表される呼び出し可能なメソッドとシグナルのセットである。<br>
*: D-Busシステムが知っているローカルIDを返す。
<br>
<br>
インターフェースは、メッセージを渡すソフトウェアの間で、インターフェースの名前、パラメータ(もしあれば)、戻り値(もしあれば)を定義する"契約"を提供する。<br>
* QDBusConnection sender()
*: シグナルを送信した接続を返す。
<br>
<br>
これらのメソッドは、インターフェイスを使用しているソフトウェアのメソッドやAPIに1対1で直接マッピングされることもある。(直接マッピングされないこともある)<br>
* QDBusConnection sessionBus()
これにより、複数のソフトウェアが、内部の実装に関係なく、類似または同一のインターフェイスを提供することができ、<br>
*: セッションバスに対して開いた<code>QDBusConnection</code>クラスのインスタンスを返す。
一方で、ソフトウェアは、ソフトウェアの内部設計を気にすることなく、これらのインターフェイスを使用できるようになる。<br>
<br>
<br>
インターフェイスは、文書化やコードの再利用を目的として、XMLで記述する。<br>
* QDBusConnection systemBus()
ユーザや開発者は、インターフェースのXML記述を参照できるだけでなく、開発者はXMLから自動生成されたクラスを使用することができる。<br>
*: システムバスに対して開いた<code>QDBusConnection</code>クラスのインスタンスを返す。
このため、D-Busの使用は非常に簡単で、エラーが発生しにくくなる。(例. コンパイラがコンパイル時にメッセージの構文を確認することができる)<br>
<br>
<br>
==== サービス ====
* QDBusPendingCall asyncCall(const QDBusMessage &message, int timeout = -1) const
サービスとは、ソフトウェアとバスの接続を表すものである。<br>
*: メッセージは、この接続を通じてメッセージを送信して、直ちにリターンする。
*: このメソッドは、メソッド呼び出しのみをサポートしており、レスポンスを追跡するために使用される<code>QDBusPendingCall</code>クラスのインスタンスを返す。
<br>
<br>
ここでいうサービスとは、D-Busの用語でいうバス名に相当するものである。<br>
* QDBusMessage call(const QDBusMessage & message, QDBus::CallMode mode = QDBus::Block, int timeout = -1 ) const
(<u>バス名という用語は、バス上の接続名であり、バスの名前ではない。</u>そのため、Qtのドキュメントで記載されているように、サービスという用語を使用する)<br>
*: メッセージは、この接続を通じて送信・ブロックして、応答を待つ。
<br>
<br>
これらは、複数のコンポーネントの名前空間を必要とする他の多くのシステムで見られるように、"逆ドメイン名"アプローチを使用することにより、一意に保たれる。<br>
* bool registerObject(const QString &path, QObject *object, RegisterOptions options = ExportAdaptors)
KDEプロジェクトのソフトウェアが提供する多くのサービスでは、サービス名にorg.kdeというプレフィックスを使用している。<br>
*: 第2引数で指定した現在のクラスのインスタンスを、第1引数に指定したQ-DBusオブジェクト名に登録する。
そのため、セッションバスにおいて、org.kde.screensaverが宣伝されているのを見掛けるかもしれない。<br>
*: 第3引数には、D-Busに公開されるオブジェクトの数を指定する。
*: 登録に成功した場合は、<code>true</code>を返す。
<br>
<br>
サービス名には、開発者の組織やソフトウェアのドメイン名を使用することを推奨する。<br>
* bool registerService(const QString &serviceName)
例えば、開発者のドメインがawesomeapps.org、ソフトウェアの名前がwickedwidgetの場合、バス上のサービス名はorg.awesomeapps.wickedwidgetとなる。<br>
*: 第1引数に指定したバス名(D-Busサービス名)に登録する。
*: 登録に成功した場合は<code>true</code>、指定したバス名(D-Busサービス名)が他のソフトウェアで既に登録されている場合は登録に失敗して<code>false</code>を返す。
<br>
<br>
ソフトウェアが複数のバスに接続している場合、または、同一のソフトウェアの複数のインスタンスが同時にアクティブになっている場合は、接続ごとに一意なサービス名を使用する必要がある。<br>
 
多くの場合、プロセスIDをサービス名に付加することでこれを実現する。<br>
==== qdbuscpp2xmlコマンド ====
<code>qdbuscpp2xml</code>コマンドは、<code>QObject</code>クラスを継承したクラスのヘッダファイル等をパースして、D-Busインターフェースファイルを生成する。<br>
スロットの引数が<code>const</code>で宣言されている場合は入力、非<code>const</code>の場合は出力とみなされることがある。<br>
qdbuscpp2xml <入力オプション> <QObjectクラスを継承したクラスを定義したヘッダファイル> <出力オプション> <生成するD-Busインターフェースファイル名>
例. qdbuscpp2xml -A SampleHelper.h -o org.qt.sample.xml
<br>
<br>
==== オブジェクト ====
<code>qdbuscpp2xml</code>コマンドのオプションを、以下に示す。<br>
ソフトウェアは、バス上の複数のオブジェクトへのアクセスを宣伝する可能性が高い。<br>
* -p または -s または -m
*: スクリプト化されたアトリビュート(<code>-p</code>オプション)、シグナル(<code>-s</code>オプション)、メソッド(スロット関数)(<code>-m</code>オプション)のみがパースされる。
* -P または -S または -M
*: 全てのアトリビュート(<code>-P</code>オプション)、シグナル(<code>-S</code>オプション)、メソッド(スロット関数)(<code>-M</code>オプション)を解析する。
* -a
*: スクリプト化された内容を全て出力する。(<code>-psm</code>オプションと等価)
* -A
*: 全てのコンテンツを出力する。(<code>-PSM</code>オプションと等価)
* -o <生成するD-Busインターフェースファイル名>
*: D-Busインターフェースファイルを生成する。
<br>
<br>
オブジェクトとサービスの間のこの多対1の関係は、アドレスにパスコンポーネントを提供することで対応される。<br>
以下の例では、C++クラスからD-Busインターフェイスファイル (XMLファイル) を生成している。<br>
<br>
<br>
サービスに関連付けられた各パスは、異なる一意のオブジェクトを表す。(例. /MainInterface、/Documents/Doc1)<br>
まず、インターフェイスを定義したC++クラスを作成する。<br>
実際のパス構造は完全に任意であり、どのようなパスにするかは、サービスを提供するソフトウェア次第である。<br>
<syntaxhighlight lang="c++">
これらのパスは、他のソフトウェアにメッセージを送信するソフトウェアのために、オブジェクトを識別し、論理的にグループ化する方法を提供する。<br>
// 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;
    }
};
</syntaxhighlight>
<br>
<br>
一部のライブラリは、オブジェクトを適切に名前空間化するために、オブジェクトパスの先頭に"リバースドメイン"を付けてエクスポートする。<br>
次に、<code>qdbuscpp2xml</code>コマンドを実行して、D-Busインターフェースファイル (XMLファイル) を生成する。<br>
これは、任意のサービスに参加するライブラリやプラグインではよくあることで、そのため、ソフトウェアや他のコンポーネントによりエクスポートされたオブジェクトとの全ての衝突を避けなければならない。<br>
以下の例では、ExampleObject.hファイルからインターフェイスを読み取り、ExampleInterface.xmlファイルを生成している。<br>
しかし、KDEソフトウェアやライブラリでは、この方法は使用されていない。<br>
qdbuscpp2xml -m -s ExampleObject.h -o ExampleInterface.xml
<br>
<br>
オブジェクトはインターフェースへのアクセスを提供しており、オブジェクトは同時に複数のインタフェースへのアクセスを提供することができる。<br>
生成されたD-Busインターフェースファイル (XMLファイル) を以下に示す。<br>
<syntaxhighlight lang="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>
</syntaxhighlight>
<br>
<br>
==== アドレスの例 ====
 
D-Busメッセージには、上記で記載したコンポーネントで構成されるアドレスが含まれており、正しいソフトウェア、オブジェクト、メソッドコールにルーティングされる。<br>
==== qdbusxml2cppコマンド ====
  # 例. KRunnerのアドレス
<code>qdbusxml2cpp</code>コマンドは、D-Busインターフェースファイルの定義に従い、C++のアダプターコードを生成する。<br>
  org.kde.krunner /App org.kde.krunner.App.display
これは、<code>qdbusabstractadapter</code>と<code>QDBusAbstractInterface</code>を継承したプロセス通信サーバとクライアントの実装コードを自動生成する。<br>
  # 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
   
   
  org.kde.krunner    : サービス
  # 各クラスに個別のインターフェースを宣言している場合は、各インターフェースごとにアダプターを生成する
  /App                : オブジェクトへのパス
  qdbusxml2cpp -m -a <生成するcppファイル名とヘッダファイル名> -c <自動生成するヘルパークラス名 (親クラス)> -i <対象となるクラスを記述しているヘッダファイル> -l <対象となるクラス名> <D-Busインターフェースファイルのパス> <D-Busインターフェース名>
  org.kde.krunner.App : オブジェクトがエクスポートするインターフェース
  例. qdbusxml2cpp -m -a SamplesAdaptor -c SamplesAdaptor -i SampleHelper.h -l SampleHelper org.qt.policykit.examples.xml org.qt.policykit.examples2
display            : インターフェース内のメソッド
<br>
<br>
もし、/Appオブジェクトがorg.kde.krunner.Appインターフェースを提供するのみの場合(または、実装するサービスの中で表示メソッドが一意の場合)、これはアドレスとして同様に機能する。<br>
<code>qdbusxml2cpp</code>コマンドのオプションを、以下に示す。<br>
  org.kde.krunner /App display
* <code>-a <アダプターソースコードおよびヘッダのファイル名></code> または <code>-A <アダプターソースコードおよびヘッダのファイル名></code>
*: 指定された名前でアダプターソースコードおよびヘッダを生成する。
* <code>-c <アダプターソースコードで使用するクラス名></code> または <code>-C <アダプターソースコードで使用するクラス名></code>
*: アダプターソースコードで使用するクラス名を指定する。
* <code>-i <対象となるクラスを記述しているヘッダファイル名></code> または <code>-I <対象となるクラスを記述しているヘッダファイル名></code>
*: アダプターソースコードに、<code>#include <対象となるクラスを記述しているヘッダファイル></code>を追加する。
* <code>-l <対象となるクラス名></code> または <code>-L <対象となるクラス名></code>
*: アダプタソースコードを生成する時に使用するクラス名(親クラス)を指定する。
* <code>-m</code>
*: アダプターソースコードに、<code>#include "<アダプターソースコード名.moc>"</code>ステートメントを追加する。
* <code>-N</code>
*: 名前空間を使用しない。
* <code>-p <アダプターソースコードのファイル名></code> または <code>-P <アダプターソースコードのファイル名></code>
*: アダプターソースコードのファイルへのプロキシコードを生成する。
<br><br>
 
== Qtプロジェクト (.pro) / CMakeLists.txt ==
* Qtプロジェクト (.pro) を使用する場合
<syntaxhighlight lang="make">
  QT += dbus
</syntaxhighlight>
<br>
<br>
このようにして、可能性のあるそれぞれの宛先を一意かつ確実にアドレスで指定することができる。<br>
* CMakeLists.txtを使用する場合
<syntaxhighlight lang="cmake">
# 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
)
</syntaxhighlight>
<br><br>
<br><br>


== メソッドとシグナル ==
== 使用例 ==
バス上の任意のエンドポイントをアドレス指定する方法を使用して、メッセージを送受信する場合の可能性を検討する。<br>
==== pingの送受信 ====
以下の例では、Qtにおいて、D-Busを使用したメッセージの送受信を行っている。<br>
# セッションバスへの接続を取得する。
# セッションバスへのD-Busインターフェースを作成する。
# D-Busインターフェイス名でのサービス登録する。
# バス上に誰が存在しているかを確認する。
# pingリクエストを受信するために登録する。
# Pingメッセージの送信する。
# 受信したPingメッセージの表示する。
<br>
受信したPingメッセージの表示以外は、doItメソッドで行う。<br>
Pingメッセージは、pingReceivedメソッドで処理される。<br>
<br>
プログラムを実行すると、セッションバス上に既に多くのサービスが表示されるが、"-- end --"の付近にPingリクエストに応答していることがわかる。<br>
<br>
<syntaxhighlight lang="c++">
#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;
}
</syntaxhighlight>
<br>
サンプルコードの詳細を知りたい場合は、以下に示すGithubを参照すること。<br>
https://github.com/PacktPublishing/Hands-On-Embedded-Programming-with-Qt/blob/master/Chapter10/DBusBruteForce/MyObject.cpp<br>
<br>
==== Firewalldの利用 ====
以下の例では、Firewalldにおいて、以下に示すようなコマンドと同等のものをD-Busを使用して実行している。<br>
sudo firewall-cmd --permanent --zone=<ゾーン名> --add-port=<ポート番号>/tcp
sudo firewall-cmd --reload
<br>
以下に示すサンプルコードを実行するには、適切な権限 (root権限) が必要となることに注意する。<br>
<syntaxhighlight lang="c++">
#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;
}
</syntaxhighlight>
<br>
==== 構造体の送信 ====
以下の例では、D-Busを使用して構造体を送信している。<br>
<syntaxhighlight lang="c++">
// 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);
    }
};
</syntaxhighlight>
<br>
<br>
==== メソッド ====
<syntaxhighlight lang="c++">
受信側のソフトウェアにおいて、メソッド(関数)を実行するために送信されるメッセージのことである。<br>
// 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();
}
</syntaxhighlight>
<br>
<code>qDBusRegisterMetaType<構造体>()</code>は、QtのD-Busシステムに対して、カスタム型を登録するための関数呼び出しである。<br>
これは、カスタム型をD-Bus経由で送信する際に必須の操作である。<br>
<br>
* 型の登録
*: この関数は、QtのD-Busシステムに構造体を<u>知らせる</u>ためのものである。
*: D-Busは標準的なデータ型 (整数、文字列等) を扱うことができるが、カスタム型については明示的に登録する必要がある。
*: <br>
* シリアライズとデシリアライズ
*: この登録により、Qtはカスタム型をどのようにシリアライズ (バイトストリームに変換) して、デシリアライズ (バイトストリームから元の型に戻す) すべきかを理解する。
*: これは、D-Busを通じてデータを送受信する時に必要となる。
*: <br>
* マーシャリング関数の使用
*: 登録プロセスは、<code>operator<<</code>と<code>operator>></code>関数を使用する。
*: これらの関数が、カスタム型のマーシャリング (データの変換) 方法を定義している。
*: <br>
* 型の安全性
*: この登録により、D-Bus経由でカスタム型のオブジェクトを安全に送受信できるようになる。
*: Qtは、送信時にこの型をシリアライズして、受信時に正しくデシリアライズすることができる。
*: <br>
* メタオブジェクトシステムとの統合
*: この関数呼び出しは、カスタム型をQtのメタオブジェクトシステムに統合する。
*: これにより、Qtの様々な機能 (シグナル/スロットシステム等) でカスタム型が使用できるようになる。
*: <br>
* 実行時の型情報
*: この登録により、D-Busシステムは実行時にカスタム型の情報を持つことができ、適切なデータ変換を行うことができる。
<br>
したがって、<code>qDBusRegisterMetaType<カスタム型></code>関数を呼び出すことにより、カスタム型をD-Busシステムで使用可能にして、データを送受信できるようにしている。<br>
<br>
 
==== 構造体の受信 ====
<syntaxhighlight lang="c++">
// 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;
      }
    }
};
</syntaxhighlight>
<br>
<br>
メソッドの呼び出しに失敗した場合(アドレスの間違い、または、要求されたソフトウェアが動作していない等の理由でメソッドが利用できない場合)、呼び出し側のソフトウェアにエラーが返る。<br>
<syntaxhighlight lang="c++">
// 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();
}
</syntaxhighlight>
<br>
<br>
メソッドの呼び出しに成功した場合、呼び出し元のソフトウェアにオプションの戻り値が返る。(戻り値が提供されない場合でも、成功メッセージが返る)<br>
<code>Q_DECLARE_METATYPE(カスタム型)</code>は、Qtのメタオブジェクトシステムにカスタム型を登録するためのマクロである。<br>
このラウンドトリップにはオーバーヘッドがあり、パフォーマンスが重要なコードではこれを念頭に置くことが重要である。<br>
<br>
<br>
メソッド呼び出しは、常に呼び出し元のソフトウェアによって開始されて、結果として生じるメッセージは正確に1つの送信元アドレスと1つの送信先アドレスを持つ。<br>
* 型の登録
*: この宣言により、カスタム型がQtのメタオブジェクトシステムに認識される。
*: これにより、Qtの様々な機能 (シグナル/スロットシステム、プロパティシステム等) でカスタム型を使用できるようになる。
*: <br>
* QVariantとの互換性
*: カスタム型をQVariantオブジェクトに格納、および、QVariant型から取り出すことが可能になる。
*: <br>
* シリアライゼーション
*: Qtの機能を使用してカスタム型のオブジェクトをシリアライズ (バイナリデータに変換)、デシリアライズ (バイナリデータから元のオブジェクトに戻す) することができるようになる。
*: <br>
* 型の安全性
*: コンパイル時の型チェックが可能になり、カスタム型を使用する時の型の安全性が向上する。
*: <br>
* qRegisterMetaTypeとの連携
*: <code>Q_DECLARE_METATYPE</code>と<code>qRegisterMetaType</code>を併用することにより、
*: Qtのスレッド間通信やイベントシステムでもカスタム型を安全に使用できるようになる。
*: <br>
* D-Busとの連携
*: D-Busシステムでカスタム型を使用する場合、この宣言により型情報が適切に処理される。
*: <br>
* 動的プロパティ
*: Qtの動的プロパティシステムでカスタム型を使用することが可能になる。
<br>
<br>
==== シグナル ====
具体的な使用例を以下に示す。<br>
シグナルはメソッドコールに似ているが、"逆方向"に発生すること、単一の宛先に縛られないことが特徴である。<br>
<syntaxhighlight lang="c++">
// シグナル / スロットでの使用
signals:
    void hogeChanged(const Hoge &newHoge);
// QVariant型での使用
Hoge myHoge;
QVariant variant = QVariant::fromValue(myHoge);
// D-Busでの使用
QDBusMessage message;
message << QVariant::fromValue(myHoge);
</syntaxhighlight>
<br>
<br>
シグナルは、インターフェイスをエクスポートしているソフトウェアから発信されて、同一バス上のどのソフトウェアでも利用できる。<br>
<u>※注意</u><br>
これにより、ソフトウェアは、状態の変化やその他のイベントを、それらの変化を追跡するソフトウェアに自発的に知らせることができる。<br>
<u><code>Q_DECLARE_METATYPE</code>はヘッダファイル内で使用して、</u><br>
<u>対応する<code>qRegisterMetaType</code>関数呼び出しは、一般的に、<code>main</code>関数内や型を使用する前に1度だけ行う必要がある。</u><br>
<br>
<br>
これは、Qtのシグナルとスロットの仕組みに似ており、これは、同じ機能のシステム版である。<br>
<u>このマクロを使用することにより、カスタム型がQtのシステムにシームレスに統合されて、D-Bus通信を含む様々なQtの機能で安全かつ効率的に使用できるようになる。</u><br>
<br><br>
<br><br>


== 便利なツール ==
== Qtプロジェクト ==
D-Busのバスの検索やD-Busを使用したソフトウェアの開発には、いくつかのエンドユーザ向けの便利なツールが存在する。<br>
Qtプロジェクトにおいて、Qt Creator付属の<code>qdbusxml2cpp</code>コマンドを実行することにより、D-Busインターフェースのアダプタクラスを生成する。<br>
生成されるファイルは、D-Busインタフェースファイル(XML形式)に対するアダプタを実装したC++のソースコードファイルとヘッダファイルである。<br>
# 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
<br>
<br>
==== qdbus ====
生成されたファイルをQtプロジェクトに指定およびインクルードする。<br>
<code>qdbus</code>コマンドは、コマンドラインツールであり、与えられたバス上のサービス、オブジェクト、インタフェースを表示したり、バス上の与えられたアドレスにメッセージを送信するために使用できる。<br>
これは、セッションバスおよびシステムバスの両方を検索するために使用することができる。 <br>
<br>
<br>
<code>--system</code>オプションを付加する場合は、qdbusはシステムバスに接続する。<br>
または、Qtプロジェクトファイルにおいて、<code>qdbusxml2cpp</code>コマンドを自動実行する設定を記述してもよい。<br>
<code>--system</code>オプションを付加しない場合は、セッションバスを使用する。<br>
system(qdbusxml2cpp -a <生成するcppファイル名とヘッダファイル名> -c <自動生成するヘルパークラス名 (親クラス)> -i <対象となるクラスを記述しているヘッダファイル> -l <対象となるクラス名> <D-Busインターフェースファイルのパス>)
<br><br>
 
== CMakeプロジェクト ==
CMakeプロジェクトにおいて、<code>qt_add_dbus_adaptor</code>コマンドを指定することにより、D-Busインターフェースのアダプタクラスを生成することができる。<br>
<code>qt_add_dbus_adaptor</code>コマンドは、Qt D-Bus XMLコンパイラ (qdbusxml2cpp) のアダプタモードでの呼び出しを設定する。<br>
<br>
<br>
<code>qdbus</code>コマンドは、与えられた引数を、与えられたオブジェクトに渡すためのアドレス、および、パラメータ(存在する場合)として使用する。<br>
第2引数で指定したD-Busインタフェースファイル(XML形式)に対するアダプタを実装したC++のソースコードファイルとヘッダファイルを生成する。<br>
もし、完全なアドレスが与えられなかった場合、バス上のその地点から利用可能な全てのオブジェクトを表示する。<br>
生成されたファイルのパスが第1引数に追加される。<br>
<br>
<br>
例えば、アドレスが提供されない場合は、利用可能なサービスのリストが表示される。<br>
第3引数には、D-Busインターフェースに基づく親クラスのヘッダファイル名を指定する。<br>
サービス名を指定する場合、オブジェクトのパスが提供される。<br>
生成されるアダプタを実装したソースコードには、<code>#include "<第3引数で指定したヘッダファイル名>"</code>としてインクルードされる。<br>
パスを指定する場合、全てのインターフェースの全てのメソッドが表示される。<br>
<br>
<br>
このように、非常に簡単にバス上のオブジェクトを検索して操作することができる。<br>
第4引数には、第3引数のクラス名(D-Busインターフェースに基づく親クラス名)を指定する。(省略可能)<br>
第5引数には、生成するヘッダファイル名(拡張子.hは不要)を指定する。(省略可能)<br>
第6引数には、生成するアダプタのクラス名を指定する。(省略可能)<br>
<br>
<br>
==== qdbusviewer ====
第4引数から第6引数までを省略する場合、親クラス名、ヘッダファイル名、クラス名は、第3引数の指定値から自動的に生成される。<br>
<code>qdbusviewer</code>は、Qtで作成されたGUIソフトウェアであり、<code>qdbus</code>コマンドがCUIから提供する機能と基本的に同一のものをGUIとして提供している。<br>
<syntaxhighlight lang="cmake">
<code>qdbusviewer</code>は、Qt本体と同梱されており、オブジェクトアドレス等のD-Busの基本概念に慣れていれば、誰でも簡単に使用できる。<br>
qt_add_dbus_adaptor(
  <任意の変数名>             # 生成されるソースファイル名を指定  例: SampleAdaptor.cpp  SampleAdaptor.h
  <D-Busインタフェースファイル>   # XML形式
  <親クラスのヘッダファイル名>
  <第3引数のクラス名>         # 省略可能
  <生成するヘッダファイル名>     # 省略可能
                            # 拡張子.hは不要
  <生成するアダプタのクラス名>   # 省略可能
)
</syntaxhighlight>
<br><br>
<br><br>
{{#seo:
|title={{PAGENAME}} : Exploring Electronics and SUSE Linux | MochiuWiki
|keywords=MochiuWiki,Mochiu,Wiki,Mochiu Wiki,Electric Circuit,Electric,pcb,Mathematics,AVR,TI,STMicro,AVR,ATmega,MSP430,STM,Arduino,Xilinx,FPGA,Verilog,HDL,PinePhone,Pine Phone,Raspberry,Raspberry Pi,C,C++,C#,Qt,Qml,MFC,Shell,Bash,Zsh,Fish,SUSE,SLE,Suse Enterprise,Suse Linux,openSUSE,open SUSE,Leap,Linux,uCLnux,Podman,電気回路,電子回路,基板,プリント基板
|description={{PAGENAME}} - 電子回路とSUSE Linuxに関する情報 | This page is {{PAGENAME}} in our wiki about electronic circuits and SUSE Linux
|image=/resources/assets/MochiuLogo_Single_Blue.png
}}


__FORCETOC__
__FORCETOC__
[[カテゴリ:Qt]]
[[カテゴリ:Qt]]

2025年1月13日 (月) 04:43時点における最新版

概要

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