Development/Tutorials/D-Bus/Accessing Interfaces (de): Difference between revisions

From KDE TechBase
(Translation in Progress)
({{Proposed_deletion|reason=This is a translated page, but the English version of this page does not have content anymore.}})
 
(12 intermediate revisions by 3 users not shown)
Line 1: Line 1:
{{Template:I18n/Language Navigation Bar|Development/Tutorials/D-Bus/Accessing_Interfaces}}
{{Proposed_deletion|reason=This is a translated page, but the English version of this page does not have content anymore.}}


{{TutorialBrowser|
{{TutorialBrowser|
Line 14: Line 14:


== Zusammenfassung ==
== Zusammenfassung ==
D-Bus ermöglicht es Anwendungen, ihr internes API, der Außenwelt zugänglich zu machen. Auf diese APIs kann zur Laufzeit über den D-Bus, unter Zuhilfenahme von Kommandozeilenprogrammen oder D-Bus Bibliotheken und Bindungen, zugegriffen werden. Dieses Tutorial befasst sich mit der letzteren Methode, mit praxisnahen Beispielen, die Sie in Ihren Anwendungen verwenden können.
D-Bus ermöglicht es, das interne API von Anwendungen, für die Außenwelt zugänglich zu machen. Auf diese APIs kann zur Laufzeit über den D-Bus, unter Zuhilfenahme von Kommandozeilenprogrammen oder D-Bus Bibliotheken und Bindungen, zugegriffen werden. Dieses Tutorial befasst sich mit der letzteren Methode, mit praxisnahen Beispielen, die Sie in Ihren Anwendungen verwenden können.


== QDBusMessage benutzen ==
== QDBusMessage benutzen ==
Line 31: Line 31:
Als Beispiel für den Zugriff auf einen Dienst der (fiktiven) <tt>ping</tt>-Methode für das {{path|/network}} object in der <tt>org.foo.bar</tt>, könnte man dies zu tun:
Als Beispiel für den Zugriff auf einen Dienst der (fiktiven) <tt>ping</tt>-Methode für das {{path|/network}} object in der <tt>org.foo.bar</tt>, könnte man dies zu tun:


<code cppqt n>
<syntaxhighlight lang="cpp-qt" line>
QDBusMessage m = QDBusMessage::createMethodCall("org.foo.bar",  
QDBusMessage m = QDBusMessage::createMethodCall("org.foo.bar",  
     "/network",  
     "/network",  
Line 37: Line 37:
     "ping");
     "ping");
bool queued = QDBusConnection::sessionBus().send(m);
bool queued = QDBusConnection::sessionBus().send(m);
</code>
</syntaxhighlight>


In Zeile 5 im obigen Beispiel haben wir die Nachrichten-Warteschlange für das Senden über den aktuelle Session-Bus. Der Rückgabewert vom Typ <tt>bool</tt> lässt uns wissen, ob das Einstellen in die Warteschlange erfolgreich war, oder nicht.
In Zeile 5 im obigen Beispiel haben wir die Nachrichten-Warteschlange für das Senden über den aktuelle Session-Bus. Der Rückgabewert vom Typ <tt>bool</tt> lässt uns wissen, ob das Einstellen in die Warteschlange erfolgreich war, oder nicht.
Line 50: Line 50:




<code cppqt n>QDBusMessage m = QDBusMessage::createMethodCall("org.foo.bar",
<syntaxhighlight lang="cpp-qt" line>QDBusMessage m = QDBusMessage::createMethodCall("org.foo.bar",
                                               "/network",
                                               "/network",
                                               "",
                                               "",
Line 57: Line 57:
args.append("kde.org");
args.append("kde.org");
m.setArguments(args);
m.setArguments(args);
bool queued = QDBusConnection::sessionBus().send(m);</code>
bool queued = QDBusConnection::sessionBus().send(m);</syntaxhighlight>


Alternativ bietet {{qt|QDBusMessage}} mit der  "operator<<" Funktion eine bequeme Methode zum Anhängen von Parametern an eine Nachricht. Das obige Beispiel wird zu:
Alternativ bietet {{qt|QDBusMessage}} mit der  "operator<<" Funktion eine bequeme Methode zum Anhängen von Parametern an eine Nachricht. Das obige Beispiel wird zu:


<code cppqt n>QDBusMessage m = QDBusMessage::createMethodCall("org.foo.bar",
<syntaxhighlight lang="cpp-qt" line>QDBusMessage m = QDBusMessage::createMethodCall("org.foo.bar",
                                               "/network",
                                               "/network",
                                               "",
                                               "",
                                               "ping");
                                               "ping");
m << "kde.org";
m << "kde.org";
bool queued = QDBusConnection::sessionBus().send(m);</code>
bool queued = QDBusConnection::sessionBus().send(m);</syntaxhighlight>


{{note|Die Argumente müssen in der {{qt|QList}} in der gleichen Reihenfolge, wie von der aufgerufenen D-Bus Methode erwartet, erscheinen.}}
{{note|Die Argumente müssen in der {{qt|QList}} in der gleichen Reihenfolge, wie von der aufgerufenen D-Bus Methode erwartet, erscheinen.}}
Line 75: Line 75:
Diese blockiert, bis eine Antwort oder ein Timeout eintritt. Wenn unsere <tt>ping</tt> Methode Informationen über den im Parameter übergebenen Host zurückgibt, könnten wir den Code folgendermaßen abändern, um an diese Informationen zu gelangen:
Diese blockiert, bis eine Antwort oder ein Timeout eintritt. Wenn unsere <tt>ping</tt> Methode Informationen über den im Parameter übergebenen Host zurückgibt, könnten wir den Code folgendermaßen abändern, um an diese Informationen zu gelangen:


<code cppqt n>QDBusMessage m = QDBusMessage::createMethodCall("org.foo.bar",
<syntaxhighlight lang="cpp-qt" line>QDBusMessage m = QDBusMessage::createMethodCall("org.foo.bar",
                                               "/network",
                                               "/network",
                                               "",
                                               "",
                                               "ping");
                                               "ping");
m << "kde.org";
m << "kde.org";
QDBusMessage response = QDBusConnection::sessionBus().call(m);</code>
QDBusMessage response = QDBusConnection::sessionBus().call(m);</syntaxhighlight>


Die <tt>response</tt> (Antwort) ist von einem der Typen: <tt>QDBusMessage::ReplyMessage</tt> oder <tt>QDBusMessage::ErrorMessage</tt>, je nachdem ob es erfolgreich war oder nicht. Man kann die zurückgegebenen Werte durchsehen, indem man die Argumente mit der <tt>{{qt|QDBusMessage}}::arguments()</tt> Methode holt, die einen <tt>{{qt|QList}}&lt;{{qt|QVariant}}&gt;</tt>. zurückgibt.
Die <tt>response</tt> (Antwort) ist von einem der Typen: <tt>QDBusMessage::ReplyMessage</tt> oder <tt>QDBusMessage::ErrorMessage</tt>, je nachdem ob es erfolgreich war oder nicht. Man kann die zurückgegebenen Werte durchsehen, indem man die Argumente mit der <tt>{{qt|QDBusMessage}}::arguments()</tt> Methode holt, die einen <tt>{{qt|QList}}&lt;{{qt|QVariant}}&gt;</tt>. zurückgibt.
Line 101: Line 101:
Hier ist ein Beispiel der Benutzung von {{qt|QDBusInterface}}, das wir Zeile für Zeile durchgehen werden:
Hier ist ein Beispiel der Benutzung von {{qt|QDBusInterface}}, das wir Zeile für Zeile durchgehen werden:


<code cppqt n>
<syntaxhighlight lang="cpp-qt" line>
QString hostname("kde.org");
QString hostname("kde.org");
QDBusConnection bus = QDBusConnection::sessionBus();
QDBusConnection bus = QDBusConnection::sessionBus();
Line 136: Line 136:
connect(interface, SIGNAL(interfaceUp(QString)),
connect(interface, SIGNAL(interfaceUp(QString)),
         this, SLOT(interfaceUp(QString)));
         this, SLOT(interfaceUp(QString)));
</code>
</syntaxhighlight>


=== Synchrone Aufrufe ===
=== Synchrone Aufrufe ===
Line 153: Line 153:
In Zeile 16 wird die <tt>ping</tt> Methode erneut aufgerufen, aber diesmal der Rückgabewert in einer Objektvariablen vom Typ {{qt|QDBusReply}} festgehalten. Es folgt die Prüfung auf gültige Werte (dass z.B. keine Fehler (error) zurückgeliefert wurden und der Rückgabetyp ein <tt>int</tt> ist), und die anschließende Verwertung der Rückgabedaten in einem "Pop-Up"-Fenster (Zeile 23).
In Zeile 16 wird die <tt>ping</tt> Methode erneut aufgerufen, aber diesmal der Rückgabewert in einer Objektvariablen vom Typ {{qt|QDBusReply}} festgehalten. Es folgt die Prüfung auf gültige Werte (dass z.B. keine Fehler (error) zurückgeliefert wurden und der Rückgabetyp ein <tt>int</tt> ist), und die anschließende Verwertung der Rückgabedaten in einem "Pop-Up"-Fenster (Zeile 23).


=== Asynchronous Method Calls and Signals ===
=== Asynchrone Methodenaufrufe und Signale ===


Up to this point in the example all of the calls made were synchronous and the application would block until a reply was received. The last two uses of {{qt|QDBusInterface}} in the example show asynchronous usage of D-Bus, and in both cases we rely on Qt's signal and slot mechanism.
Bis zu diesem Punkt im Beispiel sind alle Aufrufe synchron und die Anwendung blockiert, bis eine Antwort empfangen wurde. Die letzten beiden Verwendungen von {{qt|QDBusInterface}} im Beispiel, zeigen die asynchrone Nutzung des D-Bus und in beiden Fällen ist es der Qt-Signal-und Slot-Mechanismus auf den man sich verlässt.


On line 29 we use <tt>callWithCallback</tt> and provide a regular QObject slot to be called when the D-Bus reply returns. This way the application will not block as <tt>callWithCallback</tt> returns immediately after queueing the message to be sent on the bus. Later, the <tt>interfaceList</tt> slot would get called. Note that this method requires a <tt>{{qt|QList}}&lt;{{qt|QVariant}}&gt;</tt>; there is no shortcut for us this time.
In Zeile 29 wird ein <tt>callWithCallback</tt> benutzt und dabei ein regulärer QObject Slot angegeben, der aufgerufen wird, wenn die D-Bus Antwort zurückkommt. Auf diese Weise blockiert die Anwendung nicht, da <tt>callWithCallback</tt>, sofort nach Einstellen der Nachricht in die bus Warteschlange zum senden, wieder zurrückkehrt. Der <tt>interfaceList</tt> Slot wird dann später aufgerufen. Zu beachten ist, dass diese Methode ein <tt>{{qt|QList}}&lt;{{qt|QVariant}}&gt;</tt> erfordert; da führt erstmal kein Weg drumherum.


Finally, on line 33 we connect to a D-Bus signal. Using {{qt|QDBusInterface}} to do this looks exactly like connecting to a regular, local signal in our own application. We even use the standard <tt>{{qt|QObject}}::{{qt|connect}}</tt> method! This is accomplished by {{qt|QDBusInterface}} using Qt's meta object system to dynamically add the signals the D-Bus interface advertises. Very slick!
Abschließend wird in Zeile 33 und 34 der Slot mit einem D-Bus Signal verbunden. Die Verwendung von {{qt|QDBusInterface}} sieht genauso aus, wie das Verbinden zu einem regulärem lokalen Signal, in QT Programmen. Es wird einfach die Standard <tt>{{qt|QObject}}::{{qt|connect}}</tt> Methode angewandt. Dies wird von  {{qt|QDBusInterface}} bewerkstelligt, indem es Qt's meta object system benutzt um dynamisch die Signale, die die D-Bus Schnittstellen anbieten, hinzuzufügen. Ziemlich ausgefuchst!


=== Is ''This'' the Best Way? ===
=== Ist ''dies'' der beste Weg? ===


This ease of use over {{qt|QDBusMessage}} does come with some prices, however. First, since {{qt|QDBusInterface}} is a {{qt|QObject}} it carries the overhead that implies. It also will perform at least one round-trip to the requested D-Bus object on creation to set up the interface object with things such as the available signals. Often this additional overhead is negligible in the larger scheme of things and well made up for by the convenience it provides.
Diese einfache Bedienung über {{qt|QDBusMessage}} hat allerdings ihren Preis. Da {{qt|QDBusInterface}} ein {{qt|QObject}} ist, schleppt es den entsprechenden Overhead mit. Es generiert bei seiner Erzeugung, auch eine Abfrage an das angeforderten D-Bus Objekt, um die Schnittstelle mit Dingen, wie den verfügbaren Signalen, zu initialisieren. Oft wird dieser zusätzliche Overhead, im größeren Zusammenhang, zu vernachlässigen sein und durch die gebotene Bequemlichkeit ausgeglichen.


There are still some annoyances we have to deal with, however, such as having to know the name of the interface, setting up the correct {{qt|QDBusReply}} object such as we did above by templating it with an <tt>int</tt> and having to debug method name typos and the like at runtime versus letting the compiler do it for us. So while it's an improvement over {{qt|QDBusMessage}}, it's still not perfect.
Es gibt allerdings noch ein paar Ärgernisse zu bewältigen. Wie z.B., dass der Name der Schnittstelle bekannt sein muss, das {{qt|QDBusReply}} Objekt muss, so wie oben durch ein Template mit <tt>int</tt>, korrekt gesetzt sein und nach wie vor bleibt das Debuggen von Schreibfehlern in Methodennamen und die Entscheidung etwas zur Laufzeit, oder zur Kompilierzeit zu tun. Auch wenn es mit {{qt|QDBusMessage}} ein Fortschritt ist, es ist noch nicht perfekt.


And that's precisely where <tt>qdbusxml2cpp</tt> comes to our rescue.
Und genau hier kommt mit <tt>qdbusxml2cpp</tt>, die Rettung.


== Using Classes Generated From D-Bus XML ==
== D-Bus XML generierte Klassen benutzen ==


What would be truly great is if we could simply instantiate a local object that represented a given service and start using it right away. Perhaps something like this:
Am besten wäre es, wenn man lokale Objekte, die einen gegebenen Dienst repräsentieren, einfach erzeugen und gleich benutzen könnte. Ungefähr so wie:


<code cppqt n>
<syntaxhighlight lang="cpp-qt" line>
org::foo::bar::network *interface =  
org::foo::bar::network *interface =  
     new org::foo::bar::network("org.foo.bar", "/network",
     new org::foo::bar::network("org.foo.bar", "/network",
Line 179: Line 179:
                             this);
                             this);
interface->ping("kde.org");
interface->ping("kde.org");
</code>
</syntaxhighlight>


Fortunately for us, this is precisely what Qt allows us to do. The only requirement is an XML file describing the D-Bus service. Such files are installed in the D-Bus prefix in the {{path|interfaces}} directory.  
Glücklicherweise ist dies genau das, was QT uns erlaubt. Die einzige Vorraussetzung ist eine XML Datei, die den D-Bus Dienst beschreibt. Solche Dateien werden im D-Bus prefix im {{path|interfaces}} Verzeichnis installiert.


{{tip|The D-Bus prefix can be found by issuing the following command in a terminal:<br><tt><nowiki>pkg-config dbus-1 --variable=prefix</nowiki></tt>}}
{{tip|Das D-Bus prefix findet man heraus, indem man den folgenden Befehl auf der Kommandozeile eingibt:<br><tt><nowiki>pkg-config dbus-1 --variable=prefix</nowiki></tt>}}


We can also create our own XML files from the C++ header files and use those directly. This is covered in the next tutorial, [[../Creating_Interfaces|Creating D-Bus Interfaces]].
Man kann seine XML Dateien auch aus C++ Header Dateien erzeugen. Dies wird im nächsten Tutorial [[../Creating_Interfaces|Creating D-Bus Interfaces]] erläutert.


With the path to the XML in hand, we then add something like this to our {{path|CMakeLists.txt}}:
Mit dem Pfad zur XML Datei zur Hand, kann man dann so etwas wie den folgenden Code zu seiner {{path|CMakeLists.txt}} hinzufügen:


<code>
<syntaxhighlight lang="text">
PKGCONFIG_GETVAR(dbus-1 prefix DBUS_PREFIX)
PKGCONFIG_GETVAR(dbus-1 prefix DBUS_PREFIX)
set(network_xml ${DBUS_PREFIX}/interfaces/org.foo.bar.xml)
set(network_xml ${DBUS_PREFIX}/interfaces/org.foo.bar.xml)
qt4_add_dbus_interface(myapp_SRCS ${network_xml} network_interface )
qt4_add_dbus_interface(myapp_SRCS ${network_xml} network_interface )
</code>
</syntaxhighlight>


This will generate two files at build time, {{path|network_interface.h}} and {{path|network_interface.cpp}}, and add them to the compiled application. We can then simply <tt>#include "network_interface.h"</tt> and use the generated class as seen in the example above.
Dies erzeugt zwei Dateien während des Build-Prozess, {{path|network_interface.h}} und {{path|network_interface.cpp}}, und fügt sie der kompilierten Anwendung hinzu. Man kann einfach  <tt>#include "network_interface.h"</tt> angeben und die erzeugte Klasse, wie im oben gezeigten Beispiel, benutzen.


Examining the generated header file, we can see exactly what methods, signals as well as their signatures and return values are according to the provider of the service. Using the class directly will let the compiler do type checking on method calls leaving fewer run-time breakages to track down.
Wenn man die erzeugte Header Datei betrachtet, sieht man genau, welche Methoden, Signale, so wie ihre Signaturen und Rückgabewerte dem Anbieter des Dienstes entsprechen.Benutzt man die Klasse direkt, wird der Compiler eine Typ-Prüfung auf Methodenaufrufe durchführen, was weniger Laufzeit Brüche übriglässt, die es aufzuspüren gilt.


Due to the generated class being a subclass of {{qt|QDBusAbstractInterface}} just as {{qt|QDBusInterface}} is, anything we can do with {{qt|QDBusInterface}} is also available to us.
Da die erzeugte Klasse eine Unterklasse von {{qt|QDBusAbstractInterface}} ist,
so wie auch {{qt|QDBusInterface}}, ist alles was  {{qt|QDBusInterface}} bietet, ebenfalls verfügbar.


Due to this combination of ease of use and compile-time checking, this is generally the preferred mechanism to use when accessing D-Bus interfaces.
Durch diese Kombination aus einfacher Benutzung und Kompilierzeit-Typprüfung, ist dies der allgemein zu bevorzugende Mechanismus, um auf den D-Bus zuzugreifen.


{{tip|If your CMake installation does not provide the PKGCONFIG_GETVAR, you can add [[Development/Tutorials/D-Bus/Accessing Interfaces/PkgConfigGetVar.cmake|this cmake module]] to your project.}}
{{tip|Wenn die verwendete CMake Installation PKGCONFIG_GETVAR nicht anbietet, kann man [[Development/Tutorials/D-Bus/Accessing Interfaces/PkgConfigGetVar.cmake|this cmake module]] zu seinemm Projekt hinzufügen.}}


== Doing A Little Introspection ==
== Doing A Little Introspection ==

Latest revision as of 20:17, 11 October 2023

 
Proposed for Deletion
This page has been proposed for deletion for the following reason:

This is a translated page, but the English version of this page does not have content anymore.

Zugriff auf D-Bus Schnittstellen
Tutorial Series   D-Bus
Previous   Einführung
What's Next   Creating Interfaces, Intermediate D-Bus
Further Reading   n/a

Zusammenfassung

D-Bus ermöglicht es, das interne API von Anwendungen, für die Außenwelt zugänglich zu machen. Auf diese APIs kann zur Laufzeit über den D-Bus, unter Zuhilfenahme von Kommandozeilenprogrammen oder D-Bus Bibliotheken und Bindungen, zugegriffen werden. Dieses Tutorial befasst sich mit der letzteren Methode, mit praxisnahen Beispielen, die Sie in Ihren Anwendungen verwenden können.

QDBusMessage benutzen

QDBusMessage repräsentiert eine D-Bus Nachricht, die über einen bestimmten Bus gesendet oder empfangen wird. Jede Nachricht kann, abhängig von ihrem Einsatzzweck, von einem der folgenden Typen sein:

  • Methodenaufruf (method call)
  • Signal (signal)
  • Antwort (reply)
  • Fehler (error)

Ein Aufzählungstyp (enum), der diese 4 Typen beinhaltet, wird in QDBusMessage definiert. Auf einen Nachrichtentyp kann über die QDBusMessage::type() Methode zugegriffen werden.

Der Aufruf einer D-Bus Methode

Eine QDBusMessage kann direkt mit der statischen Methode QDBusMessage::createMethodCall( const QString & service, const QString & path, const QString & interface, const QString & method ) benutzt werden, um Methoden in D-Bus Diensten aufzurufen. Der Rückgabewert ist ein QDBusMessage Objekt, welches dann für den eigentlichen Aufruf benutzt wird.

Der interface Parameter ist optional und nur erforderlich, wenn die Methode nicht eindeutig identifizierbar im, mit dem Pfad assoziierten, Objekt ist.Dies kann passieren, wenn das Objekt mehrere Schnittstellen implementiert, die Methoden, mit den gleichen Namen haben. In solchen (seltenen) Fällen, gibt es keine Garantie darüber, welche Methode tatsächlich aufgerufen wird, wenn man die gewünschte Schnittstelle nicht explizit definiert. Allerdings kann man in der Regel einfach eine leere Zeichenfolge (z.B. ""), als Argument für interface übergeben.

Als Beispiel für den Zugriff auf einen Dienst der (fiktiven) ping-Methode für das /network object in der org.foo.bar, könnte man dies zu tun:

QDBusMessage m = QDBusMessage::createMethodCall("org.foo.bar", 
    "/network", 
    "",
    "ping");
bool queued = QDBusConnection::sessionBus().send(m);

In Zeile 5 im obigen Beispiel haben wir die Nachrichten-Warteschlange für das Senden über den aktuelle Session-Bus. Der Rückgabewert vom Typ bool lässt uns wissen, ob das Einstellen in die Warteschlange erfolgreich war, oder nicht.

Dies lässt noch zwei Fragen offen:

  • Wie werden die Parameter für einen Methodenaufruf korrekt gesetzt?
  • Wie kommt man an die Rückmeldung im Falle der D-Bus-Methoden, die einen Rückgabewert haben?

Parameter setzen

Argumente zusammen mit dem Aufruf der Methode zu senden, ist ganz einfach. Zunächst muss eine QList of QVariant objects erzeugt, und diese dann der D-Bus-Nachricht hinzugefügt werden. Wenn man also der ping Methode in der obigen Deklaration einen Hostnamen als Parameter übergibt, könnte man den Code folgenderweise abändern (beachte Zeile 5 bis 7):


QDBusMessage m = QDBusMessage::createMethodCall("org.foo.bar",
                                              "/network",
                                              "",
                                              "ping");
QList<QVariant> args;
args.append("kde.org");
m.setArguments(args);
bool queued = QDBusConnection::sessionBus().send(m);

Alternativ bietet QDBusMessage mit der "operator<<" Funktion eine bequeme Methode zum Anhängen von Parametern an eine Nachricht. Das obige Beispiel wird zu:

QDBusMessage m = QDBusMessage::createMethodCall("org.foo.bar",
                                              "/network",
                                              "",
                                              "ping");
m << "kde.org";
bool queued = QDBusConnection::sessionBus().send(m);
Note
Die Argumente müssen in der QList in der gleichen Reihenfolge, wie von der aufgerufenen D-Bus Methode erwartet, erscheinen.


Antworten erhalten

Wenn man Informationen wieder von der D-Bus Methode zurückbekommen möchte, wendet man statt dessen die Methode QDBusConnection::call an. Diese blockiert, bis eine Antwort oder ein Timeout eintritt. Wenn unsere ping Methode Informationen über den im Parameter übergebenen Host zurückgibt, könnten wir den Code folgendermaßen abändern, um an diese Informationen zu gelangen:

QDBusMessage m = QDBusMessage::createMethodCall("org.foo.bar",
                                               "/network",
                                               "",
                                               "ping");
m << "kde.org";
QDBusMessage response = QDBusConnection::sessionBus().call(m);

Die response (Antwort) ist von einem der Typen: QDBusMessage::ReplyMessage oder QDBusMessage::ErrorMessage, je nachdem ob es erfolgreich war oder nicht. Man kann die zurückgegebenen Werte durchsehen, indem man die Argumente mit der QDBusMessage::arguments() Methode holt, die einen QList<QVariant>. zurückgibt.

Ist das der beste Weg?

QDBusMessage direkt auf diese Weise zu benutzen, um D-Bus Methoden aufzurufen, ist nicht der einfachste, beste, oder empfohlene Weg. Werfen wir einen Blick auf die weitaus bequemer zu handhabende Klasse QDBusInterface um dann den Remote-Zugriff auf D-Bus Schnittstellen zu betrachten, als wären sie lokale Methoden, die automatisch aus XML heraus generierte Proxy-Klassen benutzen.

QDBusInterface anwenden

QDBusInterface bietet eine einfache und direkte Methode, um D-Bus Aufrufe und Verbindungen zu D-Bus Signalen herzustellen.

Ein QDBusInterface Objekt stellt eine gegebene D-Bus Schnittstelle dar. Der Konstruktor erwartet als Parameter (in dieser Reihenfolge) einen Dienst-Namen, einen Objekt-Pfad, eine optionale Schnittstelle und ebenfalls optional, welchen Bus (z.B. system oder session) es benutzen soll. Wenn kein Bus explizit angegeben ist, wird standardmäßig der Session-Bus benutzt. Wenn keine Schnittstelle angegeben ist, wird das zurückgegebene Objekt alle Schnittstellen auf dem Bus ansprechen.

Es wird empfohlen, explizit eine Schnittstelle zu benennen, wenn man den Konstruktor von QDBusInterface aufruft. Aufgrund der Interna des QtDBus, wird bei Leerlassen des Schnittstellen-Parameters eine Methode der Remote-Anwendung aufgerufen, die prüft, welche Methoden überhaupt verfügbar sind. Dies ist vermeidbarer Overhead. Andererseits kann QTDBus bei Übergabe eines Schnittstellennamens, das Ergebnis für weiteren Gebrauch zwischenspeichern.


Da QDBusInterface ein QObject ist, kann man ihm ein Elternobjekt übergeben. Dies erleichtert die Verwaltungsarbeit, die mit dem Erzeugen neuer QDBusInterface Objekte einhergeht, indem man QT das Aufräumen überlässt, wenn das Elternobjekt zerstört wird.

Hier ist ein Beispiel der Benutzung von QDBusInterface, das wir Zeile für Zeile durchgehen werden:

QString hostname("kde.org");
QDBusConnection bus = QDBusConnection::sessionBus();
QDBusInterface *interface = new QDBusInterface("org.foo.bar",
                                               "/network",
                                               "org.foo.bar.network", 
                                               bus,
                                               this); 

interface->call("ping");
interface->call("ping", hostname);

QList<QVariant> args;
args.append("kde.org");
interface->callWithArgumentList("ping", args);

QDBusReply<int> reply = interface->call("ping",
                                        hostname);
if (reply.isValid())
{
     KMessageBox::information(winId(), 
                              i18n("Ping to %1 took %2s")
                               .arg(hostname)
                               .arg(reply.value()),
                              i18n("Pinging %1")
                               .arg(hostname));
}

args.clear();
interface->callWithCallback("listInterfaces", args,
                            this,
                            SLOT(interfaceList(QDBusMessage));

connect(interface, SIGNAL(interfaceUp(QString)),
        this, SLOT(interfaceUp(QString)));

Synchrone Aufrufe

Zuallererst wird in Zeile 3 ein QDBusInterface erzeugt, welches desselbe Objekt wie in den obigen QDBusMessage Beispielen anspricht.

Dann werden mit einigen unterschiedlichen Techniken, verschiedene D-Bus Methoden aufgerufen. In Zeile 9 erfolgt ein einfacher Aufruf einer Methode namens ping, ohne weitere Argumente. In Zeile 10 wird die selbe Methode mit einem Parameter angesprochen. Zu beachten ist der Umstand, dass keine QList<QVariant> für die Argumentübergabe erzeugt werden muss. Man kann auf diese Weise bis zu 8 Argumente an eine D-Bus Methode übergeben.

Wenn mehr als 8 Argumente benötigt werden, um einen Zustand auszudrücken, oder aus anderen Gründen eine QList<QVariant> der bessere Ansatz ist, etwas zu bewerkstelligen, dann sollte man stattdessen eine callWithArgumentList Methode, wie in Zeile 12-14 zu sehen, einsetzen.

Der Umgang mit Antworten

In Zeile 16 wird die ping Methode erneut aufgerufen, aber diesmal der Rückgabewert in einer Objektvariablen vom Typ QDBusReply festgehalten. Es folgt die Prüfung auf gültige Werte (dass z.B. keine Fehler (error) zurückgeliefert wurden und der Rückgabetyp ein int ist), und die anschließende Verwertung der Rückgabedaten in einem "Pop-Up"-Fenster (Zeile 23).

Asynchrone Methodenaufrufe und Signale

Bis zu diesem Punkt im Beispiel sind alle Aufrufe synchron und die Anwendung blockiert, bis eine Antwort empfangen wurde. Die letzten beiden Verwendungen von QDBusInterface im Beispiel, zeigen die asynchrone Nutzung des D-Bus und in beiden Fällen ist es der Qt-Signal-und Slot-Mechanismus auf den man sich verlässt.

In Zeile 29 wird ein callWithCallback benutzt und dabei ein regulärer QObject Slot angegeben, der aufgerufen wird, wenn die D-Bus Antwort zurückkommt. Auf diese Weise blockiert die Anwendung nicht, da callWithCallback, sofort nach Einstellen der Nachricht in die bus Warteschlange zum senden, wieder zurrückkehrt. Der interfaceList Slot wird dann später aufgerufen. Zu beachten ist, dass diese Methode ein QList<QVariant> erfordert; da führt erstmal kein Weg drumherum.

Abschließend wird in Zeile 33 und 34 der Slot mit einem D-Bus Signal verbunden. Die Verwendung von QDBusInterface sieht genauso aus, wie das Verbinden zu einem regulärem lokalen Signal, in QT Programmen. Es wird einfach die Standard QObject::connect Methode angewandt. Dies wird von QDBusInterface bewerkstelligt, indem es Qt's meta object system benutzt um dynamisch die Signale, die die D-Bus Schnittstellen anbieten, hinzuzufügen. Ziemlich ausgefuchst!

Ist dies der beste Weg?

Diese einfache Bedienung über QDBusMessage hat allerdings ihren Preis. Da QDBusInterface ein QObject ist, schleppt es den entsprechenden Overhead mit. Es generiert bei seiner Erzeugung, auch eine Abfrage an das angeforderten D-Bus Objekt, um die Schnittstelle mit Dingen, wie den verfügbaren Signalen, zu initialisieren. Oft wird dieser zusätzliche Overhead, im größeren Zusammenhang, zu vernachlässigen sein und durch die gebotene Bequemlichkeit ausgeglichen.

Es gibt allerdings noch ein paar Ärgernisse zu bewältigen. Wie z.B., dass der Name der Schnittstelle bekannt sein muss, das QDBusReply Objekt muss, so wie oben durch ein Template mit int, korrekt gesetzt sein und nach wie vor bleibt das Debuggen von Schreibfehlern in Methodennamen und die Entscheidung etwas zur Laufzeit, oder zur Kompilierzeit zu tun. Auch wenn es mit QDBusMessage ein Fortschritt ist, es ist noch nicht perfekt.

Und genau hier kommt mit qdbusxml2cpp, die Rettung.

D-Bus XML generierte Klassen benutzen

Am besten wäre es, wenn man lokale Objekte, die einen gegebenen Dienst repräsentieren, einfach erzeugen und gleich benutzen könnte. Ungefähr so wie:

org::foo::bar::network *interface = 
    new org::foo::bar::network("org.foo.bar", "/network",
                            QDBusConnection::sessionBus(),
                            this);
interface->ping("kde.org");

Glücklicherweise ist dies genau das, was QT uns erlaubt. Die einzige Vorraussetzung ist eine XML Datei, die den D-Bus Dienst beschreibt. Solche Dateien werden im D-Bus prefix im interfaces Verzeichnis installiert.

Tip
Das D-Bus prefix findet man heraus, indem man den folgenden Befehl auf der Kommandozeile eingibt:
pkg-config dbus-1 --variable=prefix


Man kann seine XML Dateien auch aus C++ Header Dateien erzeugen. Dies wird im nächsten Tutorial Creating D-Bus Interfaces erläutert.

Mit dem Pfad zur XML Datei zur Hand, kann man dann so etwas wie den folgenden Code zu seiner CMakeLists.txt hinzufügen:

PKGCONFIG_GETVAR(dbus-1 prefix DBUS_PREFIX)
set(network_xml ${DBUS_PREFIX}/interfaces/org.foo.bar.xml)
qt4_add_dbus_interface(myapp_SRCS ${network_xml} network_interface )

Dies erzeugt zwei Dateien während des Build-Prozess, network_interface.h und network_interface.cpp, und fügt sie der kompilierten Anwendung hinzu. Man kann einfach #include "network_interface.h" angeben und die erzeugte Klasse, wie im oben gezeigten Beispiel, benutzen.

Wenn man die erzeugte Header Datei betrachtet, sieht man genau, welche Methoden, Signale, so wie ihre Signaturen und Rückgabewerte dem Anbieter des Dienstes entsprechen.Benutzt man die Klasse direkt, wird der Compiler eine Typ-Prüfung auf Methodenaufrufe durchführen, was weniger Laufzeit Brüche übriglässt, die es aufzuspüren gilt.

Da die erzeugte Klasse eine Unterklasse von QDBusAbstractInterface ist, so wie auch QDBusInterface, ist alles was QDBusInterface bietet, ebenfalls verfügbar.

Durch diese Kombination aus einfacher Benutzung und Kompilierzeit-Typprüfung, ist dies der allgemein zu bevorzugende Mechanismus, um auf den D-Bus zuzugreifen.

Tip
Wenn die verwendete CMake Installation PKGCONFIG_GETVAR nicht anbietet, kann man this cmake module zu seinemm Projekt hinzufügen.


Doing A Little Introspection

It may also be helpful to find out if a given service is available or to check which application is providing it. Another QDBusAbstractInterface subclass, QDBusConnectionInterface, provides methods to query for such information as which services are registered and who owns them.

Once you have a service name, you can then use QDBusInterface to get the org.freedesktop.DBus.Introspectable interface and call Introspect on it. This will return an XML block describing the objects, which can in turn be introspected for what they provide. The XML itself can be processed using QDomDocument, making it a fairly simple process.

The qdbus application that ships with Qt4 provides a nice example of code doing exactly this. It can be found in tools/qdbus/tools/qdbus/qdbus.cpp in the Qt4 source distribution.