Qtの基礎 - D-Bus
概要
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等)に送信して、中央サーバは通知を表示して、通知が閉じられたりアクションが実行されたりといったイベントを送り返すものである。
- freedesktop.orgのnotification仕様
- 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プロジェクトファイルを使用する場合は、変数QT
にdbus
オプションを追加する必要がある。
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つの複合型を規定しており、
ARRAY
、STRUCT
、マップ / ディクショナリ
が存在する。 ARRAY
は、0個以上の同一の要素からなる集合体である。STRUCT
は、異なる型の固定数の要素で構成されるコレクションである。マップ / ディクショナリ
は、要素のペアのARRAY
である。マップは0個以上の要素を持つことができる。
- D-Busでは、ネイティブの型を集約した3つの複合型を規定しており、
- 拡張された型システム
- Qt D-Busライブラリにおいて、カスタムデータ型を使用する場合、任意のクラスに
Q_DECLARE_METATYPE()
をQtメタタイプとして宣言して、 qDBusRegisterMetaType
関数で登録する必要がある。- ストリーム演算子は、登録システムによって自動的に検出されます。
- Qt D-Busライブラリは、Qtコンテナクラスがストリーム演算子関数を実装せずに、
QMap
やQList
のような配列やマップを使用するためのテンプレート特殊化を提供している。 - それ以外の型では、フローオペレータが実装を表示する必要がある。
- Qt D-Busライブラリにおいて、カスタムデータ型を使用する場合、任意のクラスに
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++のアダプターコードを生成する。
これは、qdbusabstractadapter
とQDBusAbstractInterface
を継承したプロセス通信サーバとクライアントの実装コードを自動生成する。
# 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を使用したメッセージの送受信を行っている。
- セッションバスへの接続を取得する。
- セッションバスへのD-Busインターフェースを作成する。
- D-Busインターフェイス名でのサービス登録する。
- バス上に誰が存在しているかを確認する。
- pingリクエストを受信するために登録する。
- Pingメッセージの送信する。
- 受信した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は不要
<生成するアダプタのクラス名> # 省略可能
)