入り口に戻る

原文はこちら。

原文は CC-BY-SA-4.0 で公開されており、本サイトのこのページも同じ。

# D-Bus のインターフェイスを作成する

独自の D-Bus インターフェイスを作成・使用して自分のアプリケーションの機能を公開する(expose)方法を学ぶ。XML 定義ファイル(XML descriptions)の生成、実行時のインターフェイスのインスタンス化、CMake を使った構築システムの設定について説明する。

### 目次

- 概要
- ライト：インターフェイスを定義する
  - メソッドを定義する
  - インターフェイスに命名する
- カメラ：インターフェイスを生成する
  - 簡単な方法
  - 複雑であるが移植性の高い方法
  - 方法その１：自分の手で qdbuscpp2xml を呼び出す
  - 方法その２：CMake を利用して qdbuscpp2xml を呼び出す(推奨)
  - CMake を利用してインターフェイスを公開する
- アクション：実行時にインターフェイスをインスタンス化する
  - 簡単な方法
  - 複雑な方法

## 概要

アプリケーションは、D-Bus を用いることによって、遠隔呼び出しが可能なインターフェイスを通じて内部の API を外の世界に公開できるようになる。このチュートリアルでは、自分のアプリケーションでそうしたインターフェイスを作成し実装する方法を説明する。

## ライト：インターフェイスを定義する

一般的に D-Bus インターフェイスは、同インターフェイスを提供するアプリケーションの内部の１つ以上のクラスの API を反映している。D-Bus を跨いでこの API を使えるようにするには、QDBusAbstractAdaptor の下位クラスを作成して使用する。QDBusAbstractAdaptor の下位クラスは、D-Bus メッセージに反応して直接動作を行う。但し、こうした動作は通常、別のオブジェクトにある同じような名前のメソッドを呼び出す１行のメソッドを実行するものにすぎない。このような盥回しは、D-Bus の XML 定義ファイル(D-Bus XML description file)を生成することで、ほぼ確実に避けることができる。

バス上に公開されるインターフェイスは、D-Bus 仕様で定められている標準 XML 形式を使用して記述(定義)することができる。

こうした XML は、次のようなものになる。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <!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="org.foo.Background"> <signal name="backgroundChanged"> </signal> <method name="refreshBackground"> </method> <method name="currentBackground"> <arg type="s" direction="out"/> </method> <method name="setBackground"> <arg type="b" direction="out"/> <arg name="name" type="s" direction="in"/> </method> </interface> </node>

`qdbus` (端末エミュレータ)や `qdbusviewer` (グラフィカル・ユーザ・インターフェイス)のようなアプリケーションを使って `org.freedesktop.DBus.Introspectable.Introspect` メソッドの結果を調べたことがある人であれば、上の例のような記述は見慣れているかもしれない。

この XML を手書きで作って或るクラスの API に対応させることは可能である。けれども、このやり方は、エラーが発生しやすく時間が掛かるだけでなく、それほど楽しいものでもない。この XML を他のアプリケーション(読者の D-Bus インターフェイスの消費者になりたいアプリケーション)から使うことができなくても良いのであれば、独自の QDBusAbstractAdaptor を書くこともできる。

幸運にも、この作業を自動化してほとんど意識しなくて済むようにする方法が存在する。即ち、「D-Bus 経由で公開したいメソッド」を持つクラスを(手作業で)作成し、Qt に同梱されているツールを使用して残りの作業を(自動で)やらせる方法である。

### メソッドを定義する

ユーザが背景となる壁紙を設定したり現在の(背景の)設定を問い合わせたりすることを可能にするインターフェイスの例を見る。このインターフェイスには３つのメソッドを用意することにする。以下のクラスの定義でそれらを確認することができる。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | #include <QObject> class Background : QObject { Q_OBJECT public: Background(QObject* parent); void doNotExportToDBus(); void refreshBackground(); QString currentBackground(); Q_SIGNALS: void doNotExportThisSignal(); void backgroundChanged(); public Q_SLOTS: bool setBackground(QString name); protected Q_SLOTS: void dbusCanNotSeeMe(); };

次に、D-Bus 経由で公開したいのは上記のメソッドのどれなのかを指定する必要がある。幸い、これは以下のオプション指定を使うだけで簡単に解決する。

- 全てのシグナルを公開する。
- 全ての「public」なスロットを公開する。
- 全てのプロパティを公開する。
- SCRIPTABLE 属性を持つシグナルのみ公開する。
- SCRIPTABLE 属性を持つ「public」なスロットのみ公開する。
- SCRIPTABLE 属性を持つプロパティのみを公開する。

上記のオプションは、好きなように組み合わせて使うこともできる。上記の例を使って望む結果を得るには、そのようにクラスの定義を調整する必要がある。調整したものは以下の通り。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | #include <QObject> class Background : QObject { Q_OBJECT public: Background(QObject* parent); void doNotExportToDBus(); Q_SIGNALS: void doNotExportThisSignal(); Q_SCRIPTABLE void backgroundChanged(); public Q_SLOTS: void refreshBackground(); QString currentBackground(); bool setBackground(QString name); protected Q_SLOTS: void dbusCanNotSeeMe(); };

公開したいメソッドを移動して public なスロットにしていること、及び公開したいシグナルを `Q_SCRIPTABLE` で修飾していることに注目してほしい。後ほど、全ての public なスロットと全ての SCRIPTABLE なシグナルとを公開するインターフェイスを作成することにする。

次は、このクラス(上で定義した通りのもの)の実装の作成に取り掛かる。

#### 豆知識

D-Bus 経由で他のアプリケーションへ API を公開する場合、他のアプリケーションやユーザは、スクリプトを通じて、そのインターフェイスで利用できる「呼び出し」に依存するようになる可能性がある。そのため、D-Bus のインターフェイスの変更は、他のアプリケーションに不具合を発生させることがある。それゆえ、アプリケーションの版番号の上位の部分(major release)が変わらない間は、公開している D-Bus API との互換性を保つのが望ましい。

### インターフェイスに命名する

自分のインターフェイスを定義し終えたので、次はバス上で表示される名前を決める段である。この名前は、名前の衝突を防ぐため、逆ドメイン名形式とするのが慣例である。したがって、プロジェクトのウェブサイトのドメインが `foo.org` である場合、自身のインターフェイスの名前の頭に `org.foo` を付け加える必要がある。

そのため、我々の例ではインターフェイスは `org.foo.Background` と名付けることにする。これをコードの中で定義するにあたっての最も簡単な方法は、マクロ `Q_CLASSINFO` のエントリをクラスの定義の中に追加する方法である。

> class Background : QObject { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.foo.Background")

これによって、作ろうとしているインターフェイスは周りから `org.foo.Background` として知られることになる。

## カメラ：インターフェイスを生成する

### 簡単な方法

インターフェイスを作成する方法として最も簡単なものは、作ったクラスを直接インターフェイスとして使用するというものである。作成したクラスの中の `Q_OBJECT` マクロの下に忘れずに `Q_CLASSINFO("D-Bus Interface", "org.foo.Background")` を入れておけば良い。これを済ませた後は、以下の節を飛ばして「実行時にインターフェイスをインスタンス化する」へ進む。

### 複雑であるが移植性の高い方法

この方法では、構築(build)の手順が複雑になる。けれども、もしプロジェクトが他の多くのアプリケーションから利用される予定のものであるならば、おそらくこちらの方法が適している。この方法では、XML ファイルがシステムにインストールされるので、他のアプリケーションはこの XML ファイルを使用して自分のアダプタ・クラスを生成することができる。読者のプロジェクトをインストールした者は、プロジェクトのソースを見に行かなくてもこのファイルを参照することができる。

自作のクラスのインターフェイスの設定をし終えたので、D-Bus と我々のアプリケーションのオブジェクトとの間を取り持つアダプタ・クラス(adaptor class)を生成しよう。第一段階として、このチュートリアルの冒頭に載せた XML を生成する。

端末エミュレータで `qdbuscpp2xml` を呼び出して手作業で XML ファイルを生成し、作られた XML ファイルを自身のプロジェクトのソースの一部として配布することができる。

また、CMake を使ってコンパイル時に XML の生成をやらせるという選択肢もある。この手法には２つの利点がある。ソースコードに余計なものを入れなくて良いことと、該当クラスを編集する度に忘れずに XML ファイルを再生成する必要がなくなることである。

### 方法その１：自分の手で qdbuscpp2xml を呼び出す

目的の XML ファイルを生成するために、Qt に付属しているコマンド・ライン・ツール `qdbuscpp2xml` を使用する。このプログラムは、C++ のソース・ファイルを入力として受け取り、D-Bus のインターフェイスの XML による定義を生成してくれる。`qdbuscpp2xml` では、以下のコマンド・ライン・スイッチを使用することによって、どのメソッドを公開するかを選ぶことができる。

スイッチ | 公開するもの
--- | ---
-S | 全てのシグナル
-M | 全ての public なスロット
-P | 全てのプロパティ
-A | 公開可能なもの全て
-s | SCRIPTABLE 属性のあるシグナル
-m | SCRIPTABLE 属性のある public なスロット
-p | SCRIPTABLE 属性のあるプロパティ
-a | SCRIPTABLE 属性のあるもの全て

上述の例において、public なスロットは全て公開したいが、シグナルは SCRIPTABLE 属性のあるものだけを公開したい。そのため、次のコマンドを使う。

> $> qdbuscpp2xml -M -s background.h -o org.foo.Background.xml

この命令によって `org.foo.Background.xml` という名前のファイルが生成される。同ファイルの中身は次のようになる。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <!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="org.foo.Background"> <signal name="backgroundChanged"> </signal> <method name="refreshBackground"> </method> <method name="currentBackground"> <arg type="s" direction="out"/> </method> <method name="setBackground"> <arg type="b" direction="out"/> <arg name="name" type="s" direction="in"/> </method> </interface> </node>

このファイルは、自分のプロジェクトのソースの配布物一式に同梱するものとする。

### 方法その２：CMake を利用して qdbuscpp2xml を呼び出す(推奨)

自分のプロジェクトの `CMakeLists.txt` に次のコードを書き加える。

> set(my_nice_project_SRCS ${my_nice_project_SRCS} ${CMAKE_CURRENT_BINARY_DIR}/org.foo.Background.xml ) qt5_generate_dbus_interface( background.h org.foo.Background.xml OPTIONS -a ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/org.foo.Background.xml DESTINATION ${DBUS_INTERFACES_INSTALL_DIR})

これによって `background.h` から `org.foo.Background.xml` が生成され、生成された `org.foo.Background.xml` は `${DBUS_INTERFACES_INSTALL_DIR}` にインストールされる。`${DBUS_INTERFACES_INSTALL_DIR}` は通常、`/usr/share/dbus-1/interfaces` を指す。

### CMake を利用してインターフェイスを公開する

続いて、この XML を自分のプロジェクトに加える。これは、`CMakeLists.txt` ファイルに次の一文を追加することによって実現される。

> qt5_add_dbus_adaptor(my_nice_project_SRCS org.foo.Background.xml background.h Background)

この一文により、構築ディレクトリに２つのファイル(この例では `backgroundadaptor.h` と `backgroundadaptor.cpp`)が生成される。この２つのファイルは、構築時に合わせて構築されてアプリケーションに追加される。これらのファイルを自身のプロジェクトのソースの配布物一式に同梱してはいけない。

D-Bus XML 定義ファイル(XML description file)も同時にインストールされる。これによって、ユーザは同ファイルをリファレンスとして参照することができるようになり、他のアプリケーションはこのファイルと `qdbusxml2cpp` とを用いてインターフェイス・クラスを生成することができるようになる(`qdbusxml2cpp` の使い方は「D-Bus インターフェイスへアクセスする」のチュートリアルで見た)。

こうして生成されたアダプタを使用して「実行時にインターフェイスをインスタンス化する」ことができる。

## アクション：実行時にインターフェイスをインスタンス化する

### 簡単な方法

`QDBusConnection::registerObject` を使用して自分のクラスを登録することができる。通常は、自分のクラスのコンストラクタの中でこの登録作業を行うが、同一のクラスのインスタンスを複数使用する場合、DBus の経路(path)が被らないように注意する必要がある。シングルトン・クラス(singleton class)の場合、コンストラクタの中に次のようなコードを書くやり方がある。

1 2 3 4 5 6 7 8 9 10 | #include <QDBusConnection> Background::Background(QObject* parent) : QObject(parent) { // org.kde.myapp/foobar に DBus オブジェクトを登録 QDBusConnection::sessionBus().registerService("org.kde.myapp"); QDBusConnection::sessionBus().registerObject("/foobar", this, QDBusConnection::ExportScriptableContents); ... // コンストラクタの残りの部分 }

６行目において、サービスの経路(path)を DBus に登録している。この経路名は、他のプロジェクトで重ねて使用してはならない。関数 `main` の中に下記のコードがある場合、サービス名 `org.kde.myapp` は自動で登録されるので、上記のコンストラクタの６行目を省略しても安全である。

> KLocalizedString::setApplicationDomain("myapp"); KDBusService service(KDBusService::Unique);

７行目において、このクラスを `org.kde.myapp/foobar/org.foo.Background` に存在するオブジェクトとして登録している。ルートへの経路(root path)である `/` を指定する場合を除いて、第１引数の末尾が `/` であってはならない。第２引数は、自分が DBus へ公開したいと考えているクラスへのポインタである。このクラスは、`QObject` の下位クラスでなければならず、その定義に `Q_CLASSINFO("D-Bus Interface", "org.foo.Background")` の１文を含んでいなければならない。第３引数は、DBus へ公開したいメソッド(の種類)である(詳しくは「Qt のドキュメントの QDBusConnection のページ」を参照)。

しかしながら、このクラスのインスタンスを複数使用する場合、経路名(path)の衝突を避けるために上の例を修正する必要がある。`QDBusConnection::sessionBus().registerObject("/foobar", this, QDBusConnection::ExportScriptableContents);` の部分を `QDBusConnection::sessionBus().registerObject("/foobar/" + QString("YOUR UNIQUE INSTANCE IDENTIFIER"), this, QDBusConnection::ExportScriptableContents);` に書き換える(「YOUR UNIQUE INSTANCE IDENTIFIER」=「被りの無いインスタンス識別子」)。

### 複雑な方法

自分のインターフェイスを作成する方法はわかったので、あとは実行時にインターフェイスを作成する方法を学ぶだけである。これは、生成されたヘッダ・ファイルを include で取り込み、オブジェクトをインスタンス化することで実現される。コードは次の例のように書く。

1 2 3 4 5 6 7 8 9 10 11 | #include "background.h" #include "backgroundadaptor.h" Background::Background(QObject* parent) : QObject(parent) { new BackgroundAdaptor(this); QDBusConnection dbus = QDBusConnection::sessionBus(); dbus.registerObject("/Background", this); dbus.registerService("org.foo.Background"); }

生成されたアダプタは QObject であるため、コンストラクタに `this` を渡すことによって次のことが実現される。即ち、同アダプタは、オブジェクト Background が削除される時に削除されるようになるだけでなく、D-Bus 呼び出しの転送のために `this` に結びつけられるようにもなる。

次いで、`QDBusConnection::registerObject` を呼び出して、自分のオブジェクトをバスに登録する必要がある。さらに、`QDBusConnection::registerService` を呼び出して、他のアプリケーションがインターフェイスを利用できるようにインターフェイスを公開する必要もある。

### 豆知識

自作のアプリケーションの中で同じオブジェクトを複数作成する場合、それぞれのオブジェクトを他とは異なる経路(unique path)で登録する必要がある。オブジェクトの「他とは異なる名前」を決めるための十分に練られた命名規則が存在しないのであれば、ポインタ `this` が役に立つかもしれない。

前の記事

中級の D-Bus

次の記事

D-Bus で独自型を使用する

入り口に戻る