27. April 2010

Remote Procedure Calls über den DBus

Wozu dient der DBus? Und wie spricht man ihn an? Ein einfaches Beispiel in fünf verschiedenen Sprachen —- mit Java, OCaml, Python, C und der Shell!

DBus dient zur Interprozesskommunikation (IPC). Es ist ein Bussystem, über welches Prozesse mit Signalen oder Methodenaufrufe kommunizieren können. Eine Einführung gibt es auf der Entwicklerseite.

Um die Benutzung des DBus zu lernen, habe ich die Bindings zu verschiedenen Programmiersprachen ausprobiert (C, Python, Java, OCaml, sh). Als einfaches Beispiel möchte ich einem entferntem Prozess, der Objekte über den DBus exportiert, dazu veranlassen, die Hypotenuse in einem rechtwinkligen Dreieck auszurechnen, und das Ergebnis über den DBus zurückzusenden.

Jede Verbindung zum DBus-Dämon hat einen eindeutigen Namen. Zusätzlich können sich Verbindungen noch zusätzliche, lesbare Namen registrieren. In diesem Beispiel nennen wir die Verbindung /example/dbus/ServiceProvider.

Über den DBus kann man Objekte exportieren, welche eine bestimmte Schnittstelle anbieten. Im Modell des DBus hat ein Objekt folgende Eigenschaften:

  • object path – Jedes Objekt hat einen Pfad, der das Objekt identifiziert. Der Pfad ist ähnlich wie ein Dateipfad aufgebaut. In diesem Beispiel wählen wir für das (hier: einzige) exportierte Objekt den Pfad /example/dbus/hypotenuse.
  • interfaces – Jedes Objekt unterstützt mindestens eine Schnittstelle. Die Namen der Schnittstellen gleichen der Java-Konvention für vollständige Klassennamen. In unserem Beispiel wird das exportierte Objekt die Schnittstelle /example/dbus/HypotenuseService implementieren.
  • members – Jedes Objekt kann verschiedene Methoden und Signale anbieten. Im Beispiel ist dies nur die Methode hypotenuse.

Nun zum Beispiel. Ein Prozess wird über den DBus einen Dienst anbieten, der darin besteht, die Hypotenuse zu berechnen. Die Parameter und der Rückgabewert müssen mit dem DBus übermittelt werden. Diesen Prozess implementieren wir wie folgt in Java:

jea_pygments_txp: File 'HypotenuseService.java' does not exist.

Die Java-DBus-Bindings kümmern sich teilweise um das Mappen des Java-Objektmodells auf das DBus-Objektmodell. Wir benötigen noch eine Implementation der Schnittstelle:

jea_pygments_txp: File 'DefaultHypotenuseService.java' does not exist.

Schließlich brauchen wir noch eine Klasse, die das ganze miteinander verklebt, die Verbindung mit dem Session-DBus-Dämon aufbaut, und ein Objekt exportiert.

jea_pygments_txp: File 'ServiceProvider.java' does not exist.

Die Klasse ServiceProvider starten wir nun in einem eigenen Prozess, der auf RPCs über den DBus wartet. Auf meinem Rechner hat dies mit

javac -classpath /usr/share/java/dbus.jar:. example/dbus/*.java
java -classpath /usr/share/java/dbus.jar:. example.dbus.ServiceProvider

recht gut funktioniert, vorausgesetzt libdbus-java ist installiert. Wenn es funktioniert, dann können wir mit dem Kommando ListDBus aus dem Paket dbus-java-bin überprüfen, ob der Java-Prozess sich erfolgreich mit dem DBus-Dämon verbunden hat. Ist dies geschehen, können wir uns daran machen, in verschiedenen Sprachen über den DBus mit dem Prozess zu kommunizieren. Als erstes betrachten wir die Implementierung eines Klienten mit Java:

jea_pygments_txp: File 'ServiceRequestor.java' does not exist.

Man sieht, dass man für den Zugriff auf das entfernte Objekt hauptsächlich vier Informationen benötigt, den Busnamen des entfernten Prozesses, den Objektpfad, den Namen der Schnittstelle (welcher bei den Java-Bindings aus dem Klassennamen der Java-Schnittstelle extrahiert wird) und den Namen der Methode (die Java-Bindings bieten hier eine sehr angenehme Proxy-Lösung).

Mit ebenfalls wenigen Zeilen und sehr ähnlich können wir in Python den gleichen Methodenaufruf über den DBus duchführen. Dazu muss mindestens das Paket python-dbus installiert sein.

jea_pygments_txp: File 'exampledbus.py' does not exist.

Zum Entwickeln mit OCaml benötigen wir mindestens die Pakete libdbus-ocaml und libdbus-ocaml-dev, hier wurde die Version (0.07-1build1) karmic verwendet. Mit den OCaml-Bindings können wir im Unterschied zu Java oder Python nicht mit Proxy-Objekten arbeiten, sondern konstruieren uns im Baukastenprinzip einen Methodenaufruf zusammen.

jea_pygments_txp: File 'exampledbus.ml' does not exist.

Das Kompilieren des OCaml-Programms hat auf meinem Rechner erfolgreich geklappt mit

ocamlc -I /usr/lib/ocaml/dbus dBus.cma exampledbus.ml -o exampledbus 

Auch über die Shell können wir mit dem Kommando dbus-send Methoden aufrufen. Das Prinzip sollte nun wirklich nicht mehr überraschen.

jea_pygments_txp: File 'exampledbus.sh' does not exist.

Zuletzt noch der Methodenaufruf in C über die GLib-Bindings. Hier besteht vor allem die Kunst darin, das Programm zu kompilieren. Das Finden der Pfade kann man sicherlich geschickter anstellen, ich war jedoch zufrieden, das C-Programm mit

gcc -Wall -Werror \
		-I /usr/include/glib-2.0 \
		-I /usr/lib/glib-2.0/include \
		-I /usr/include/dbus-1.0 \
		-lglib-2.0 -ldbus-glib-1 \
		exampledbus.c -o exampledbus

zum Kompilieren zu überreden. Hier ist der etwas modifizierte Beispiel-Code aus dem DBus-Tutorial, zugeschnitten auf unser Beispiel:

jea_pygments_txp: File 'exampledbus.c' does not exist.

Alle obigen Beispiele verwenden nur die Möglichkeit des Remote Procedure Call. Über den DBus kann man prinzipiell auch Signale verschicken, per Broadcast an alle interessierten Prozesse, die mit dem DBus verbunden sind. Vielleicht ein andermal.