独自の D-Bus インタフェイスを作成・使用して自分のアプリケーションの機能を公開する(expose)方法を学ぶ。XML 定義ファイル(XML descriptions)の生成、実行時のインタフェイスのインスタンス化、CMake を使った構築システムの設定について説明する。
アプリケーションは、D-Bus を用いることによって、遠隔呼び出しが可能なインタフェイスを通じて内部の API を外の世界に公開できるようになる。このチュートリアルでは、自分のアプリケーションでそうしたインタフェイスを作成し実装する方法を説明する。
一般的に D-Bus インタフェイスは、同インタフェイスを提供するアプリケーションの内部の1つ以上のクラスの API を反映している。D-Bus を跨いでこの API を使えるようにするには、QDBusAbstractAdaptor の下位クラスを作成して使用する。QDBusAbstractAdaptor の下位クラスは、D-Bus メッセージに反応して直接動作を行う。但し、こうした動作は通常、別のオブジェクトにある同じような名前のメソッドを呼び出す1行のメソッドを実行するものにすぎない。このような盥回しは、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 に同梱されているツールを使用して残りの作業を(自動で)やらせる方法である。
ユーザが背景となる壁紙を設定したり現在の(背景の)設定を問い合わせたりすることを可能にするインタフェイスの例を見る。このインタフェイスには3つのメソッドを用意することにする。以下のクラスの定義でそれらを確認することができる。
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 経由で公開したいのは上記のメソッドのどれなのかを指定する必要がある。幸い、これは以下のオプション指定を使うだけで簡単に解決する。
上記のオプションは、好きなように組み合わせて使うこともできる。上記の例を使って望む結果を得るには、そのようにクラスの定義を調整する必要がある。調整したものは以下の通り。
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 の生成をやらせるという選択肢もある。この手法には2つの利点がある。ソースコードに余計なものを入れなくて良いことと、該当クラスを編集する度に忘れずに XML ファイルを再生成する必要がなくなることである。
目的の 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> |
このファイルは、自分のプロジェクトのソースの配布物一式に同梱するものとする。
自分のプロジェクトの 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
を指す。
続いて、この XML を自分のプロジェクトに加える。これは、CMakeLists.txt
ファイルに次の一文を追加することによって実現される。
qt5_add_dbus_adaptor(my_nice_project_SRCS org.foo.Background.xml background.h Background)
この一文により、構築ディレクトリに2つのファイル(この例では backgroundadaptor.h
と backgroundadaptor.cpp
)が生成される。この2つのファイルは、構築時に合わせて構築されてアプリケーションに追加される。これらのファイルを自身のプロジェクトのソースの配布物一式に同梱してはいけない。
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); ... // コンストラクタの残りの部分 } |
6行目において、サービスの経路(path)を DBus に登録している。この経路名は、他のプロジェクトで重ねて使用してはならない。関数 main
の中に下記のコードがある場合、サービス名 org.kde.myapp
は自動で登録されるので、上記のコンストラクタの6行目を省略しても安全である。
KLocalizedString::setApplicationDomain("myapp"); KDBusService service(KDBusService::Unique);
7行目において、このクラスを org.kde.myapp/foobar/org.foo.Background
に存在するオブジェクトとして登録している。ルートへの経路(root path)である /
を指定する場合を除いて、第1引数の末尾が /
であってはならない。第2引数は、自分が DBus へ公開したいと考えているクラスへのポインタである。このクラスは、QObject
の下位クラスでなければならず、その定義に Q_CLASSINFO("D-Bus Interface", "org.foo.Background")
の1文を含んでいなければならない。第3引数は、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 で独自型を使用する