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

    From KDE TechBase
    (Translation in Progress)
    (still in translation progress)
    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 Bequemlichkeit die sie bietet 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. der Name der Schnittstelle muss bekkannt sein, das {{qt|QDBusReply}} Objekt muss korrekt gesetzt sein, so wie oben durch ein Template mit <tt>int</tt> 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 ein Fortschritt über {{qt|QDBusMessage}} ist, ist es 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 ==
    == Using Classes Generated From D-Bus XML ==

    Revision as of 23:36, 12 August 2010


    Development/Tutorials/D-Bus/Accessing_Interfaces


    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 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.

    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 Bequemlichkeit die sie bietet ausgeglichen.

    Es gibt allerdings noch ein paar Ärgernisse zu bewältigen. Wie z.B. der Name der Schnittstelle muss bekkannt sein, das QDBusReply Objekt muss korrekt gesetzt sein, so wie oben durch ein Template mit int 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 ein Fortschritt über QDBusMessage ist, ist es noch nicht perfekt.

    Und genau hier kommt mit qdbusxml2cpp, die Rettung.

    Using Classes Generated From D-Bus XML

    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:

    org::foo::bar::network *interface =

       new org::foo::bar::network("org.foo.bar", "/network",
                               QDBusConnection::sessionBus(),
                               this);
    

    interface->ping("kde.org");

    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 interfaces directory.

    Tip
    The D-Bus prefix can be found by issuing the following command in a terminal:
    pkg-config dbus-1 --variable=prefix


    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 D-Bus Interfaces.

    With the path to the XML in hand, we then add something like this to our CMakeLists.txt:

    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 )

    This will generate two files at build time, network_interface.h and network_interface.cpp, and add them to the compiled application. We can then simply #include "network_interface.h" and use the generated class as seen in the example above.

    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.

    Due to the generated class being a subclass of QDBusAbstractInterface just as QDBusInterface is, anything we can do with QDBusInterface is also available to us.

    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.

    Tip
    If your CMake installation does not provide the PKGCONFIG_GETVAR, you can add this cmake module to your project.


    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.