「C++の応用 - D-Bus」の版間の差分
234行目: | 234行目: | ||
{ | { | ||
try { | try { | ||
// | // セッションバスへの接続を作成する場合 | ||
auto connection = sdbus::createSessionBusConnection(); | auto connection = sdbus::createSessionBusConnection(); | ||
// システムバスへの接続を作成する場合 | |||
//auto connection = sdbus::createSystemBusConnection(); | |||
// 指定されたD-Busサービスとオブジェクトへのプロキシオブジェクトを作成 | // 指定されたD-Busサービスとオブジェクトへのプロキシオブジェクトを作成 | ||
271行目: | 274行目: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
<br> | <br> | ||
===== CMakeを使用する場合 ===== | ===== CMakeを使用する場合 ===== | ||
<syntaxhighlight lang="cmake"> | <syntaxhighlight lang="cmake"> |
2024年7月29日 (月) 08:33時点における版
概要
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は、言語やツールキットに囚われないため、あらゆるプロバイダのソフトウェアやサービスが相互作用することができる。
デーモン(軽量サービスプロバイダ)とそれを利用したいソフトウェアが、必要なサービス以上のことを知らなくても通信できるようにするためによく利用される。
D-Busに仕様を詳しく知りたい場合は、freedesktopの公式Webサイトを参照すること。
sdbus-c++ライブラリ
sdbus-c++ライブラリとは
sdbus-c++ライブラリは、C++で理解できるAPIを提供するように設計されたLinux向けの高水準C++ D-Busライブラリである。
SystemdによるC D-Bus実装であるsd-busの上に、抽象化の別のレイヤーを追加する。
sdbus-c++ライブラリは、主にdbus-c++を置き換えるものとして開発されている。
dbus-c++は、現在多くの未解決のバグ、並行性の問題、固有の設計の複雑さと制限に悩まされている。
sdbus-c++ライブラリはsd-busライブラリを使用しているが、必ずしもSystemdに制約されているわけではなく、Systemd以外の環境でも完璧に使用することができる。
sdbus-c++ライブラリのインストール
sdbus-c++ライブラリのビルドに必要なライブラリをインストールする。
# SUSE sudo zypper install pkg-config make cmake gcc gcc-c++ expat-devel systemd-devel
sdbus-c++ライブラリのGithubにアクセスして、ソースコードをダウンロードする。
ダウンロードしたファイルを解凍する。
tar xf sdbus-cpp-<バージョン>.tar.gz cd sdbus-cpp-<バージョン>
または、git clone
コマンドを実行して、ソースコードをダウンロードすることもできる。
git clone https://github.com/Kistler-Group/sdbus-cpp.git cd sdbus-cpp
sdbus-c++ライブラリをビルドおよびインストールする。
cmake -DCMAKE_INSTALL_PREFIX=<sdbus-c++ライブラリのインストールディレクトリ> \ -DCMAKE_BUILD_TYPE=Release \ -DBUILD_CODE_GEN=ON \ -DBUILD_DOC=ON \ -DBUILD_EXAMPLES=ON \ .. make -j $(nproc) make install
使用例 : Systemdサービスユニットの開始
以下の例では、sdbus-c++ライブラリを使用して、sudo systemctl start smbコマンドと同等の操作を行っている。
D-Bus経由でSystemdと直接通信するため、より柔軟で、プログラム内から制御できるようになっている。
ただし、さんぷるコードを実行するには、適切な権限が必要となる。
#include <sdbus-c++/sdbus-c++.h>
#include <iostream>
int main()
{
try {
// D-Busへの接続を作成
auto connection = sdbus::createSystemBusConnection();
// Systemdのマネージャーインターフェースへのプロキシオブジェクトを作成
auto proxy = sdbus::createProxy(*connection,
"org.freedesktop.systemd1",
"/org/freedesktop/systemd1");
// StartUnitメソッドを呼び出してsmbサービスを開始
// "StartUnit"はSystemdサービスのメソッド名
// "org.freedesktop.systemd1.Manager"はD-Busインターフェース名
// "smb.service"は開始するSystemdサービス名
// "replace"はSystemdサービスの起動モード
proxy->callMethod("StartUnit")
.onInterface("org.freedesktop.systemd1.Manager")
.withArguments("smb.service", "replace")
.storeResultsTo();
std::cout << "smb service started successfully." << std::endl;
}
catch (const sdbus::Error &e) {
// sdbus::Errorをキャッチしてエラーメッセージを表示
std::cerr << "D-Bus error: " << e.what() << std::endl;
return -1;
}
catch (const std::exception &e) {
// std::exceptionをキャッチして一般エラーを処理
std::cerr << "Error: " << e.what() << std::endl;
return -1;
}
return 0;
}
使用例 : ユーザ定義のD-Busサービスの実行
D-Busサービス
以下に示すようなユーザ定義のD-Busサービスがあるとする。
- D-Busサービス名
- org.example.mochiu
- D-Busオブジェクト名
- /org/example/mochiu
- D-Busインターフェース名
- org.example.mochiu.method
- D-Busインターフェースメソッド名
- func1
- func1の引数
- 第1引数 int型
- 第2引数 std::stringクラスの参照
- func1の戻り値
- int型
上記のD-Busサービスにおいて、指定したD-Busサービス、オブジェクト、インターフェース、メソッドを使用して実行している。
D-Busインターフェースの定義ファイル (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>
<!-- D-Busインターフェースの定義 -->
<interface name="org.example.mochiu.method">
<!-- func1メソッドの定義 -->
<!-- func1メソッドは、2つの入力引数 (int型とstd::string型) を受け取り、int型の値を返すことを定義している -->
<method name="func1">
<!-- 第1引数 : int型 -->
<arg name="arg1" type="i" direction="in"/>
<!-- 第2引数 : std::string型 (ここでは参照型として使用する) -->
<arg name="arg2" type="s" direction="in"/>
<!-- 戻り値 : int型 -->
<arg name="result" type="i" direction="out"/>
</method>
</interface>
</node>
std::stringクラスの参照渡しは明示的に行っていないが、これは、sdbus-c++ライブラリが内部で適切に処理するためである。
sdbus-c++ライブラリは効率的な引数の受け渡しを行うよう設計されているため、大きなオブジェクトは自動的に参照として扱われる。
D-Busヘルパーファイル
まず、D-Busヘルパー実行ファイルを作成する。
// 例: mochiu_helper.hファイル
#ifndef MOCHIU_HELPER_H
#define MOCHIU_HELPER_H
#include <sdbus-c++/sdbus-c++.h>
#include <iostream>
#include <cstring>
#include <memory>
#include "mochiu_adaptor.h" // 生成されたプロキシヘッダをインクルード
class MochiuHelper : public MochiuAdaptor
{
public:
MochiuHelper(sdbus::IConnection &connection, const std::string &objectPath) : MochiuAdaptor(connection, objectPath)
{}
// func1メソッドの実装
int func1(const int &arg1, const std::string &arg2) override
{
std::cout << "func1 called with arg1: " << arg1 << " and arg2: " << arg2 << std::endl;
return arg1 * 2;
}
};
class MochiuService
{
private:
std::unique_ptr<sdbus::IConnection> m_connection;
std::unique_ptr<MochiuHelper> m_helper;
public:
MochiuService() : m_connection(sdbus::createSessionBusConnection("org.example.mochiu")),
m_helper(std::make_unique<MochiuHelper>(*m_connection, "/org/example/mochiu"))
{
m_connection->enterEventLoop();
}
};
#endif // MOCHIU_HELPER_H
次に、上記のDBusインターフェース定義ファイルからC++コードを生成する。
sdbus-c++ライブラリには、XMLファイルからC++のソースコードを生成するツールが付属している。 (sdbus-c++-xml2cppファイル)
sdbus-c++-xml2cppで生成されたソースコードを使用することにより、型安全性が向上して、D-Busインターフェースの使用がより簡単になる。
以下に示すコマンドを実行して、<クライアント側のソースコード>と<サーバ側のソースコード) を生成する。
sdbus-c++-xml2cpp <D-Busインターフェース定義ファイル名 (XMLファイル)> \ --proxy=<D-Busインターフェースメソッドが記述されたヘルパー実行ファイル側のヘッダファイル名 (プロキシヘッダ)> \ --adaptor=<生成するアダプターヘッダファイル名> # 例 : sdbus-c++-xml2cpp org.example.mochiu.xml \ --proxy=mochiu_helper.h \ --adaptor=mochiu_adaptor.h
最後に、生成されたソースコードを上記のヘルパー実行ファイルのソースコードに統合する。
// 例: mochiu_header.hファイル
#include "mochiu_adaptor.h" // 生成されたプロキシヘッダをインクルード
D-Busの呼び出し側
#include <sdbus-c++/sdbus-c++.h>
#include <iostream>
#include <cstring>
int main()
{
try {
// セッションバスへの接続を作成する場合
auto connection = sdbus::createSessionBusConnection();
// システムバスへの接続を作成する場合
//auto connection = sdbus::createSystemBusConnection();
// 指定されたD-Busサービスとオブジェクトへのプロキシオブジェクトを作成
auto proxy = sdbus::createProxy(*connection,
"org.example.mochiu", // D-Busサービス名
"/org/example/mochiu" // D-Busオブジェクト名
);
// func1メソッドの引数
int arg1 = 42;
std::string arg2 = "Hello, D-Bus!";
// D-Busインターフェースメソッド (func1) を呼び出して、戻り値を受け取る
auto result = proxy->callMethod("func1") // D-Busインターフェースメソッド名
.onInterface("org.example.mochiu.method") // D-Busインターフェース名
.withArguments(arg1, arg2) // 引数を渡す
.returnValue<int>(); // int型の戻り値を受け取る
std::cout << "Method 'func1' called successfully." << std::endl;
std::cout << "Result: " << result << std::endl;
}
catch (const sdbus::Error &e) {
// sdbus::Errorをキャッチしてエラーメッセージを表示
std::cerr << "D-Bus error: " << e.what() << std::endl;
return -1;
}
catch (const std::exception &e) {
// std::exceptionをキャッチして一般エラーを処理
std::cerr << "Error: " << e.what() << std::endl;
return -1;
}
return 0;
}
CMakeを使用する場合
cmake_minimum_required(VERSION 3.16)
project(<プロジェクト名>)
set(CMAKE_CXX_STANDARD 17)
find_package(sdbus-c++ REQUIRED)
add_executable(<ターゲット名>
main.cpp
)
target_link_libraries(<ターゲット名> PRIVATE
SDBusCpp::sdbus-c++
)