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プロジェクト (.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を使用したメッセージの送受信を行っている。
- セッションバスへの接続を取得する。
- セッションバスへの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
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_METATYPE
とqRegisterMetaType
を併用することにより、- 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は不要
<生成するアダプタのクラス名> # 省略可能
)