目次


D-BUSを使用してデスクトップ・アプリケーションを接続

アプリケーションの相互通話を援助

Comments

D-BUSとは本質的にはIPC(inter-process communication :プロセス間通信)のことですが、数々の機能がD-BUSを「そこら辺にあるどうでもいいIPC実装」と言う屈辱的な烙印とは一線を画する存在にしています。様々な種類のIPC実装がこの世に存在し、それぞれが明確に定義された特定の問題の解決を趣旨としています。CORBAはオブジェクト指向プログラミングの複雑なIPCに対する効果的な解決法です。DCOPは軽量級のIPCフレームワークをともないパワーの面でもの足りませんが、K Desktop Environmentとうまく統合します。SOAPとXML-RPCはWebサービス向けに設計されていますので、HTTPをトランスポート・プロトコルとして使用します。D-BUSはデスクトップ・アプリケーションとOSのコミュニケーションに向けて設計されています。

デスクトップ・アプリケーションの通信

典型的なデスクトップは複数の実行中アプリケーションをともない、多くの場合それぞれが相互の通信を必要とします。DCOPはKDEに対する解決法ですが、それはQtに束縛され他のデスクトップ環境では使用されません。同様に、BonoboはGNOMEに対する解決法ですが、それはCORBAを基にしているので結構重たいです。それはGObjectに縛られていますのでGNOMEの外では使われません。D-BUSは簡素なIPCとしてDCOPとBonoboに取って代わり、それら2つのデスクトップ環境を統合します。D-BUSへの依存性を最小限にとどめさせているため、D-BUSを使用したい他のアプリケーションは依存性を膨張させることを気にかける必要がありません。

デスクトップ/O.S.通信

ここで言うO.S.(Operating System)は、カーネルだけではなくシステム・デーモンをも含みます。例えば、D-BUSを使用可能なudev(動的な/devディレクトリーを提供するLinux 2.6版のdevfs)がある場合、(USBカメラのような)デバイスが挿入されれば、信号が発信されます。これによりデスクトップ上のハードウェアとのより固い統合を果たし、ユーザーの満足度を高める結果となります。

D-BUSの機能

D-BUSには様々な興味深い機能が備わり、将来性が高いと言えます。

プロトコルは短い待ち時間と低オーバーヘッドを誇り、ラウンドトリップ(round-trips)を最小限に抑えるために小型にまとめられ効率を高めています。それに加えて、プロトコルは無駄の多いシリアル化のプロセスを排除する(テキストでは無く)バイナリーです。ユース・ケースはローカル・マシンでの処理に偏っていますので、全てのメッセージはネイティブのバイト・オーダーとして送信されます。バイト・オーダーはそれぞれのメッセージに記述されますので、D-BUSメッセージがネットワークを通して遠く離れたホストに流れても正しく構文解析されます。

開発者の観点から見れば、D-BUSは扱いやすいのです。ワイヤー・プロトコルは簡単に理解でき、クライアントのライブラリーはそれを直感的な方法でラップします。

ライブラリーは別のシステムにラップされるようにも設計されています。GNOMEがGObject を使ってラッパーをD-BUSの周りに作成し(これらは部分的に存在し、D-BUSをイベント・ループと統合させます。)、KDEがQtを使用して類似するラッパーを作成することが予測されます。Pythonのオブジェクト指向性と柔軟な型定義により、かなりより簡単なインターフェースを持つPythonラッパーがすでにあります。

最後に、GNOME、KDE、そして他の分野から興味を抱いている業界人達が設計と実装に参加するfreedesktop.orgの傘のもとで、D-BUSは開発されていることをここに述べておきます。

D-BUSの内なる仕組み

典型的なD-BUSの設定は複数のバスを伴います。まず、ブート時に起動する永続的なシステム・バスがあります。オペレーティングシステムとデーモンはこのバスを使い、このバスは任意のアプリケーションがシステム・イベントを偽装できないように堅く保護されています。ユーザーがログインするときに起動しそのユーザー専用のものとして扱われる多くのsession busもあります。ユーザーのアプリケーションが通信に使用するのは、そのsession busなのです。当然ながら、アプリケーションがシステム・バスからメッセージを受信したいのであれば、それに接続するのもありですが、それが送信できるメッセージは制約されます。

一度アプリケーションがバスに接続されれば、matcherを追加することによりどのメッセージを受信したいかを記述しなくてはなりません。インターフェース、オブジェクト・パス、そしてメソッド(下記参照)を基に受信されるメッセージのルールをmatcherは指定します。これは取り扱いたいものへの取り扱いにアプリケーションが専念することを可能にし、メッセージの効率的なルーティングをうながし、バスを介する予期されるメッセージの洪水が全アプリケーションのパフォーマンスを(よたよたと這っているように映るほどに)低下させるのを防ぎます。

オブジェクト

実のところ、D-BUSはピアツーピア・プロトコルですので、全てのメッセージには送信元と宛先があります。これらのアドレスはオブジェクト・パスとして指定されます。概念上、D-BUSを使用する全てのアプリケーションはオブジェクトのセットを含み、メッセージはオブジェクト・パスに認識される特定の(アプリケーションでは無く)オブジェクトへ送信されます。

そのうえ、どのオブジェクトも1つかそれ以上のインターフェースをサポート可能です。最初は、これらのインターフェースはJavaのインターフェースまたはC++の純粋な仮想クラスに見えます。しかしながら、オブジェクトが実装すると主張するインターフェースを実装しているかどうかをチェックするオプションが無く、サポートされるインターフェースをリストするようにオブジェクトを見る手段もありません。メソッド名にネーム・スペースを与えるためにインターフェースを使用しますので、単独のオブジェクトが同一の名前を共有するメソッドを複数持ちそれぞれのインターフェースが異なる場合もあり得ます。

メッセージ

D-BUSのメッセージには、メソッド呼び出し、メソッド戻り、信号、そしてエラーの4種類があります。メソッドをD-BUSオブジェクト上で実行するには、メソッド呼び出しメッセージをオブジェクトに送信します。それはちょっとした処理を行ない、メソッド戻りメッセージまたはエラー・メッセージを戻します。何も戻さないと言う意味で、信号は他と一線を画します。(『信号戻り』メッセージやその他の種類のエラー・メッセージなどと言うものはありません。)

メッセージは任意の引き数を持つことができます。引き数は強く型定義され、型には基本的なプリミティブ(ブール値、バイト数、整数)から高レベルの構造(文字列、配列、ディクショナリー)まで様々あります。

サービス

サービスはD-BUSの抽象では最もレベルが高く、それらの実装は現時点では流動的です。アプリケーションはバスと共にサービスを登録でき、それが成功すればアプリケーションはサービスを入手したことになります。他のアプリケーションは特定のサービスがバスに存在するかどうかを確認し、存在しなければそれを起動するようにバスに命令できます。サービスの抽象の詳細(特にサービス起動)は現在開発中であり、いつ変更が入っても不思議はありません。

ユース・ケース

D-BUSが比較的新しいにもかかわらず、それはとても素早く普及してきました。先述のとおり、デバイスがホット・プラグされるときに信号を送信するようにD-BUSサポートと一緒にudevを構築できます。どのアプリケーションもそれらのイベントにlistenし受信時に処理を実行します。例えば、gnome-volume-manager はUSBメモリー・キーの挿入を検出し、自動的に装着します。また、デジタル・カメラが接続されれば、それは自動的に写真データをダウンロードします。

実用性は遥かに低いのですがより面白い例として、JamboreeとRingalingの組み合わせが挙げられます。Jamboree はD-SUBインターフェース付きの簡素な音楽プレイヤーで、再生、次の曲へのスキップ、そして音量の変更などの操作が可能です。Ringaling は、/dev/ttyS0 (シリアル・ポート)を開き何が受信されるかを監視する小さなプログラムです。Ringaling が『RING』と書かれたテキストを発見すれば、それはD-BUSを使い「音量を下げる」ようにJamboreeに伝えます。全体的な結果として発生する現象として、(コンピューターにモデムが接続されていて)電話が鳴れば、音楽の音量が下がります。これこそがコンピューターの用途の真髄なのです!

コードの例

D-BUSコードの用例をいくつか紹介します。

dbus-ping-send.c は、『Ping!』の文字列を引き数として毎秒session busに信号を送信します。ここではバスを管理するためにGLib を使用していますので、バス接続の詳細に自分自身で対処する必要はありません。

リスト1. dbus-ping-send.c
#include <glib.h>
#include <dbus/dbus-glib.h>
static gboolean send_ping (DBusConnection *bus);
int
main (int argc, char **argv)
{
  GMainLoop *loop;
  DBusConnection *bus;
  DBusError error;
  /* Create a new event loop to run in */
  loop = g_main_loop_new (NULL, FALSE);
  /* Get a connection to the session bus */
  dbus_error_init (&error);
  bus = dbus_bus_get (DBUS_BUS_SESSION, &error);
  if (!bus) {
    g_warning ("Failed to connect to the D-BUS daemon: %s", error.message);
    dbus_error_free (&error);
    return 1;
  }
  /* Set up this connection to work in a GLib event loop */
  dbus_connection_setup_with_g_main (bus, NULL);
  /* Every second call send_ping() with the bus as an argument*/
  g_timeout_add (1000, (GSourceFunc)send_ping, bus);
  /* Start the event loop */
  g_main_loop_run (loop);
  return 0;
}
static gboolean
send_ping (DBusConnection *bus)
{
  DBusMessage *message;
  /* Create a new signal "Ping" on the "com.burtonini.dbus.Signal" interface,
   * from the object "/com/burtonini/dbus/ping". */
  message = dbus_message_new_signal ("/com/burtonini/dbus/ping",
                                     "com.burtonini.dbus.Signal", "Ping");
  /* Append the string "Ping!" to the signal */
  dbus_message_append_args (message,
                            DBUS_TYPE_STRING, "Ping!",
                            DBUS_TYPE_INVALID);
  /* Send the signal */
  dbus_connection_send (bus, message, NULL);
  /* Free the signal now we have finished with it */
  dbus_message_unref (message);
  /* Tell the user we send a signal */
  g_print("Ping!\n");
  /* Return TRUE to tell the event loop we want to be called again */
  return TRUE;
}

main 関数はGLib イベント・ループを作成し、session busへの接続を入手し、D-BUSイベント取り扱いをGLibイベント・ループと統合します。そしてそれはsend_pingを呼び出す1秒間のタイマーを作成し、イベント・ループを開始します。

send_ping は、オブジェクト・パス/com/burtonini/dbus/pingそしてインターフェースcom.burtonini.dbus.Signalから来る、新規のPing信号を構成します。それから文字列「Ping!」は引き数として信号に追加され、バスを介して送信されます。メッセージは標準的な出力として表示され、信号の送信をユーザーに知らせます。

当然ですが、何もlistenしていない状態でバスを通して信号を垂れ流すのは賢くありません。そこで登場するは、

リスト2. dbus-ping-listen.c
#include <glib.h>
#include <dbus/dbus-glib.h>
static DBusHandlerResult signal_filter (DBusConnection *connection, DBusMessage *message, void *user_data);
int
main (int argc, char **argv)
{
  GMainLoop *loop;
  DBusConnection *bus;
  DBusError error;
  loop = g_main_loop_new (NULL, FALSE);
  dbus_error_init (&error);
  bus = dbus_bus_get (DBUS_BUS_SESSION, &error);
  if (!bus) {
    g_warning ("Failed to connect to the D-BUS daemon: %s", error.message);
    dbus_error_free (&error);
    return 1;
  }
  dbus_connection_setup_with_g_main (bus, NULL);
  /* listening to messages from all objects as no path is specified */
  dbus_bus_add_match (bus, "type='signal',interface='com.burtonini.dbus.Signal'");
  dbus_connection_add_filter (bus, signal_filter, loop, NULL);
  g_main_loop_run (loop);
  return 0;
}
static DBusHandlerResult
signal_filter (DBusConnection *connection, DBusMessage *message, void *user_data)
{
  /* User data is the event loop we are running in */
  GMainLoop *loop = user_data;
  /* A signal from the bus saying we are about to be disconnected */
  if (dbus_message_is_signal (message, DBUS_INTERFACE_ORG_FREEDESKTOP_LOCAL, "Disconnected")) {
    /* Tell the main loop to quit */
    g_main_loop_quit (loop);
    /* We have handled this message, don't pass it on */
    return DBUS_HANDLER_RESULT_HANDLED;
  }
  /* A Ping signal on the com.burtonini.dbus.Signal interface */
  else if (dbus_message_is_signal (message, "com.burtonini.dbus.Signal", "Ping")) {
    DBusError error;
    char *s;
    dbus_error_init (&error);
    if (dbus_message_get_args (message, &error, DBUS_TYPE_STRING, &s, DBUS_TYPE_INVALID)) {
      g_print("Ping received: %s\n", s);
      dbus_free (s);
    } else {
      g_print("Ping received, but error getting message: %s\n", error.message);
      dbus_error_free (&error);
    }
    return DBUS_HANDLER_RESULT_HANDLED;
  }
  return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}

このプログラムは、dbus-ping-send.c が出力する信号をlistenします。main関数は以前と同様に開始され、バスへの接続を作成します。そしてそれは、インターフェース(com.burtonini.dbus.Signal)付きの信号送信が通知されることを望み、signal_filterを通知の関数として設定し、そしてイベント・ループに突入することを記述します。

突き合せと一致するメッセージが送信されるとき、signal_funcが呼び出されます。しかしながら、それはバスそのものからのバスの管理信号をも受信します。メッセージを受信するときにどう対処するかは、メッセージ・ヘッダーを検査する簡単なケースです。メッセージがバスの切断信号であれば、存在しないバスにlistenする意味が無いのでイベント・ループは終了します。(信号が取り扱われていると、バスは伝えられます。)次に、着信メッセージは予測されているメッセージと比較され、もしもそれが一致すれば、引き数は抽出され出力されます。もしも着信メッセージがそのどちらでもなければ、「メッセージを取り扱わなかった。」とバスに伝わります。

これらの2つの例では、(完全なのはいいのですが、サービスそして多数のオブジェクトを作成したいのであればくどいとも言える)低レベルのD-BUSライブラリーを使用しました。ここでより高レベルなバインディングの出番となります。D-BUSの論理モデルに遥かに近いプログラミング・インターフェースをもたらす開発中のC#そしてPythonラッパーがあります。例として、ここにあるのはPythonにあるより洗練されたping/listenの例の焼き直しです。Pythonバインディングは論理インターフェースのモデルを作成しますので、サービスから来るそれが無くては信号を送信するのは不可能です。そのようなわけで、この例はサービスをも作成します。

リスト3. dbus-ping-send.py
#! /usr/bin/env python
import gtk
import dbus
# Connect to the bus
bus = dbus.Bus()
# Create a service on the bus
service = dbus.Service("com.burtonini.dbus.SignalService", bus)
# Define a D-BUS object
class SignalObject(dbus.Object):
    def __init__(self, service):
        dbus.Object.__init__(self, "/", [], service)
# Create an instance of the object, which is part of the service
signal_object = SignalObject(service)
def send_ping():
    signal_object.broadcast_signal("com.burtonini.dbus.Signal", "Ping")
    print "Ping!"
    return gtk.TRUE
# Call send_ping every second to send the signal
gtk.timeout_add(1000, send_ping)
gtk.main()

大半のコードが書かれているとおり自明の理でそのままです。バスへの接続を取得し、サービス(com.burtonini.dbus.SignalService)は登録されます。そして、最低限のD-BUSオブジェクトが作成されオブジェクトから信号が毎秒ブロードキャストされます。このコードは該当するC codeよりも明確ですが、Pythonバインディングにはまだ手直しが必要です。(例えば、このままでは信号に引き数を追加する手段がありません。)

リスト4. dbus-ping-listen.py
#! /usr/bin/env python
import gtk
import dbus
bus = dbus.Bus()
def signal_callback(interface, signal_name, service, path, message):
    print "Received signal %s from %s" % (signal_name, interface)
# Catch signals from a specific interface and object, and call signal_callback
# when they arrive.
bus.add_signal_receiver(signal_callback,
                        "com.burtonini.dbus.Signal", # Interface
                        None, # Any service
                        "/" # Path of sending object
                        )
# Enter the event loop, waiting for signals
gtk.main()

このコードはdbus-ping-listen.cにある同等のC code よりも簡潔でより簡単に解読できます。もう一度述べますが、バインディングにも改善の余地があるのです。bus.add_signal_receiverを呼び出すとき、ユーザーはインターフェースとオブジェクト・パスを提出せねばならず、そうしなければ不格好なmatcherが作成されてしまいます。これはありふれたバグです。一度それが修正されれば、サービスとオブジェクト・パスの引き数は取り除かれ、コードの読みやすさを更に向上させます。

まとめ

(使用を希望するアプリケーションに対し最低限のオーバーヘッドのコストしか課さない)D-BUSは、軽量級ですがパワーに満ちあふれるRPC(remote procedure call)システムです。D-BUSは現在非常に経験豊かなプログラマー集団により公的にかつ活動的に開発されています。早期の段階からD-BUSは先進的な人々から急速に受け入れられていますので、LinuxデスクトップでのD-BUSの将来はバラ色に明るいと言えます。


ダウンロード可能なリソース


関連トピック

  • D-BUSに関する情報、ダウンロード、関連文書、そしてその他様々なものを D-BUSのホームページにて探せます。
  • D-BUSはfreedesktop.org/の一環として開発されました。
  • ORBitは、GNOMEにて使用されるCORBA実装です。(GNOMEコンポーネント・システムであるBonoboは、ORBit上にて構築されています。)
  • KDEのRPC実装はDCOPです。
  • Project Utopiaは、Linux内でのシームレスなハードウェア統合の構築を目標とし、D-BUSでそれを達成しています。
  • 以前、(この記事の著者)Ross Burton氏はdeveloperWorksの記事PythonでGObjectをラップする(developerWorks、2003年03月)を著作し、どうすれば(C言語に堪能かどうかに関係無く)好きなときにC-codeされたGObject をPythonにて使用できるかを示しました。
  • Connect KDE applications using DCOP(developerWorks、2004年02月)はKDEのプロセス間の通信プロトコルそしてそれを記述する方法を提供します。
  • CORBA Component Model (CCM)はCORBA仕様そして(他のコンポーネント・モデルとの)CORBAインターオペラビリティーの輪郭を明確にします。
  • より多くのLinux開発者用のリソースをお探しでしたら、developerWorks Linux zoneをご覧ください。
  • Developer BookstoreのLinux部門にて、Linux関連書物を割引価格でお買い求めください。

コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Linux
ArticleID=228108
ArticleTitle=D-BUSを使用してデスクトップ・アプリケーションを接続
publish-date=07272004