C Sharpの応用 - D-Bus
概要
D-Busはメッセージバスシステムであり、ソフトウェアが互いに通信するためのシンプルな方法である。
プロセス間通信に加えて、プロセスのライフサイクルを調整するのに役立つ。
シングルインスタンスのソフトウェアやデーモンをコード化して、そのサービスが必要な時にオンデマンドでソフトウェアやデーモンを起動することにより、シンプルで信頼性の高いものになる。
Tmds.DBusは、dbus-sharpのプロトコル実装の上に構築されており、.NET 4.5で導入された非同期プログラミングモデルに基づく APIを提供している。
このライブラリは .NET Standard 2.0をターゲットとしており、.NET Framework 4.6.1以降、.NET Core / .NET 6以降で実行できる。
dbus-sharp (ndesk-dbusプロジェクトのフォーク) は、Monoと.NET 2.0をターゲットとするC#の実装である。
Tmds.DBus.Protocol
Tmds.DBus.Protocolパッケージは、D-Busプロトコルの低レベルAPIを提供する。
高レベルのTmds.DBusライブラリとは異なり、プロトコルライブラリはネイティブAOTコンパイルで使用できる。
Tmds.DBus.SourceGeneratorは、プロトコルライブラリをターゲットとしたソースジェネレータを提供する。
Tmds.DBusライブラリのインストール
Tmds.DBusライブラリをNuGetからインストールする。
また、Tmds.DBusライブラリはGithubで公開されている。
- Riderの場合
- [ツール]メニューバー - [NuGet] - [<プロジェクト名> の NuGet パッケージを管理]を選択する。
- メイン画面下部にNuGetペインが開くので、Tmds.DBusと入力して検索する。
- メイン画面右下から、プロジェクト名の右にある[+]ボタンを押下する。
- Tmds.DBusライブラリがインストールされる。
次に、dotnet
コマンドを実行して、Tmds.DBus.Toolをインストールする。
これにより、各D-BusサービスからC#のインターフェースを自動生成することができる。
NuGetからインストールする場合、インストールに失敗することに注意する。
dotnet tool install -g Tmds.DBus.Tool
D-Busサービスのインターフェースの生成
dotnet dbus list
コマンドを実行して、任意のD-Busサービス名を調べる。
# システムバスの場合 dotnet dbus list services --bus system | grep -iE "<D-Busサービス名 (名前の一部でも可能)>" # セッションバスの場合 dotnet dbus list services --bus session | grep -iE "<D-Busサービス名 (名前の一部でも可能)>"
D-Busサービス名を使用して、D-Busオブジェクト名を調べる。
# システムバスの場合 dotnet dbus list objects --bus system --service <D-Busサービス名> | head -2 # セッションバスの場合 dotnet dbus list objects --bus session --service <D-Busサービス名> | head -2
以下の例では、org.freedesktop.login1.Managerサービスがシステムバス上にあり、
org.freedesktop.login1.Managerサービスを実装した/org/freedesktop/login1オブジェクトにエントリポイントオブジェクトがあることを示している。
# 実行例: dotnet dbus list objects --bus system --service org.freedesktop.login1 | head -2 # 出力例: /org/freedesktop/LogControl1 : org.freedesktop.LogControl1 /org/freedesktop/login1 : org.freedesktop.login1.Manager
最後に、dotnet dbus codegen
コマンドを実行して、D-Busサービス向けのC#インターフェイスを生成する。
# システムバスの場合 dotnet dbus codegen --bus system --service <D-Busサービス名> # セッションバスの場合 dotnet dbus codegen --bus session --service <D-Busサービス名> # 実行例: dotnet dbus codegen --bus system --service org.freedesktop.login1
C#インターフェイスが記述されているファイルは、現在のカレントディレクトリに<D-Busサービス名のサフィックス>.DBus.csファイルとして生成される。
自動生成されたC#ファイルには、D-Busサービスのインターフェースが記述されている。
このD-Busサービスのインターフェースを使用して、プロキシオブジェクトのインスタンスを生成する。
D-Busの呼び出し (クライアント側)
以下の例では、Linuxにおいてセッションをログオフしている。
システムバスまたはセッションバスに接続するConnection.System
はStatic
であることに注意する。
Connection.System
およびConnection.Session
は、それぞれシステムバスおよびセッションバスへの接続を提供する。
これらのメンバは、アプリケーション全体で同じConnection
クラスを共有する便利な方法を提供している。
各バスへの接続は、最初の使用時に自動的に確立される。
ただし、ステートフル操作 (Connection.RegisterServiceAsync
等) は許可されていない。
※注意
実行アプリケーションは特定のD-Busサービスを要求する時、該当するD-Busサービスがまだ起動していない場合において、Connection
クラスのActivateServiceAsync
メソッドを実行することにより、
指定したD-Busサービス名に対してD-Busデーモンにリクエストを送信して、該当サービスの起動を試みる。
これは、D-Busサービスの遅延起動(必要になるまでサービスが起動されない)を可能にするためのものである。
D-Busサービスがオンデマンドで実行されるように設計されている場合に特に有用である。
したがって、D-Busサービスは使用される必要がある時にのみ起動されるため、リソースの節約に役立つ。
using Tmds.DBus;
using login1.DBus; // org.freedesktop.login1サービスをもとに自動生成されたC#インターフェース
namespace DBusSample;
internal static class Program
{
private static async Task Main(string[] args)
{
try
{
// D-Busのシステムバスに接続
var connection = Connection.System;
// ログインマネージャーオブジェクトを取得
// connection.CreateProxy<<自動生成されたインターフェース名>>("<D-Busサービス名>", new ObjectPath("<D-Busオブジェクト名"));
var loginManager = connection.CreateProxy<IManager>("org.freedesktop.login1", new ObjectPath("/org/freedesktop/login1"));
// D-Busサービスのヘルパーファイルが起動していない場合、ヘルパーファイルを起動する
// 常に起動しているD-Busサービスにおいては不要である
//await connection.ActivateServiceAsync("<D-Busサービス名>");
// セッションのリストを取得
var sessions = await loginManager.ListSessionsAsync();
// セッションをログオフする
foreach (var session in sessions)
{
await loginManager.TerminateSessionAsync(session.Item1);
}
Console.WriteLine("ログオフが完了しました。");
}
catch (Exception ex)
{
Console.WriteLine($"エラー: {ex.Message}");
}
}
}
D-Busヘルパーファイル
D-Busヘルパーファイルのサンプルコード
D-Busヘルパーファイルは、以下に示すような順で処理を記述する。
- D-Busシステムバス、または、D-Busセッションバスに接続する。
Connection
クラスのConnectAsync
メソッド
- D-Busオブジェクトを登録する。
Connection
クラスのRegisterObjectAsync
メソッド、または、RegisterObjectsAsync
メソッド
- D-Busサービスを登録する。
Connection
クラスのRegisterServiceAsync
メソッド
- D-Busサービスが呼び出されてヘルパーファイルが実行されるまで待機する。
また、一定時間内に呼び出しが無い場合は、D-Busサービスを終了することを推奨する。- 一定時間のみ待機する場合 (例: 30[Sec])
await Task.Delay(1000 * 30)
- PCをシャットダウンするまで待機する場合
while (true) { await Task.Delay(int.MaxValue) }
以下の例で使用しているD-Busヘルパーファイルのインターフェースは、以下に示すような構造をしている。
<node>
<interface name="com.example.MyInterface">
<method name="SayHello">
</method>
<method name="Add">
<arg type="i" name="x" direction="in"/>
<arg type="i" name="y" direction="in"/>
<arg type="i" name="sum" direction="out"/>
</method>
</interface>
</node>
using Tmds.DBus;
namespace DBusHelper;
// D-Busインターフェースの定義
[DBusInterface("com.example.MyInterface")] // D-Busインターフェース名を指定する 例: com.example.MyInterface
public interface IMyInterface : IDBusObject
{
Task SayHelloAsync();
Task<int> AddAsync(int x, int y);
}
// D-Busサービスの定義
public class MyObject : IMyInterface
{
// D-Busオブジェクト名
public ObjectPath ObjectPath { get; } = new ObjectPath("/com/example/MyObject");
// 各D-Busインターフェースの詳細な定義
public async Task SayHelloAsync()
{
await Task.Run(() => Console.WriteLine("Hello from D-Bus server!"));
}
public async Task<int> AddAsync(int x, int y)
{
return await Task.Run(() => x + y);
}
public Task<IDisposable> WatchPropertiesAsync(Action<PropertyChanges> onChanged)
{
return Task.FromResult<IDisposable>(null!);
}
public Task<PropertyChanges> GetAllAsync()
{
return Task.FromResult(new PropertyChanges());
}
}
class Program
{
static async Task Main(string[] _)
{
// D-Busシステムバスを使用
// D-Busセッションバスを使用する場合は、Address.Sessionを使用すること
var connection = new Connection(Address.System);
await connection.ConnectAsync();
try
{
// D-Busオブジェクトを登録
var myObject = new MyObject();
await connection.RegisterObjectAsync(myObject);
// D-Busサービスを登録
const string helperServiceName = "com.example.MyObject";
await connection.RegisterServiceAsync(helperServiceName);
Console.WriteLine("D-Bus server is start.");
// D-Busヘルパーファイルの起動時間を指定 例: 30[Sec]
await Task.Delay(1000 * 30);
Console.WriteLine("Terminates the D-Bus server.");
}
catch (Exception e)
{
Console.WriteLine($"{e.Message}");
}
finally
{
// 登録したD-BusサービスおよびD-Busオブジェクトを削除
connection.Dispose();
}
}
}
D-Busセッションバスを使用する場合
セッションバス向けD-Busサービスファイルを作成する。
sudo vi /usr/share/dbus-1/services/<任意の名前>.service
# /usr/share/dbus-1/services/<任意の名前>.serviceファイル
[D-BUS Service]
Name=<D-Busサービス名>
Exec=<D-Busヘルパーファイルのパス>
D-Busシステムバスを使用する場合
まず、システムバス向けD-Busサービスファイルを作成する。
sudo vi /usr/share/dbus-1/system-services/<任意の名前>.service
# /usr/share/dbus-1/system-services/<任意の名前>.serviceファイル
[D-BUS Service]
Name=<D-Busサービス名>
Exec=<D-Busヘルパーファイルのパス>
User=root
次に、PolicyKit (PolKit) の動作するために必要なセキュリティポリシーを設定する。
sudo vi /usr/share/dbus-1/system.d/<任意の名前>.conf
# /usr/share/dbus-1/system.d/<任意の名前>.confファイル
<policy user="root">
<allow own="<D-Busサービス名>"/>
</policy>
<policy context="default">
<allow send_destination="<D-Busサービス名>"/>
</policy>