×
Namespaces

Variants
Actions
(Difference between revisions)

QtDbus quick tutorial

From Nokia Developer Wiki
Jump to: navigation, search
vivainio (Talk | contribs)
hamishwillee (Talk | contribs)
m (Text replace - "<code cpp>" to "<code cpp-qt>")
 
(17 intermediate revisions by 6 users not shown)
Line 1: Line 1:
[[Category:Maemo]]
+
[[Category:Maemo]][[Category:Qt]][[Category:MeeGo Harmattan]][[Category:Base/System]]
 +
{{ArticleMetaData <!-- v1.2 -->
 +
|sourcecode= <!-- Link to example source code e.g. [[Media:The Code Example ZIP.zip]] -->
 +
|installfile= <!-- Link to installation file (e.g. [[Media:The Installation File.sis]]) -->
 +
|devices= <!-- Devices tested against - e.g. ''devices=Nokia 6131 NFC, Nokia C7-00'') -->
 +
|sdk= <!-- SDK(s) built and tested against (e.g. [http://linktosdkdownload/ Qt SDK 1.1.4]) -->
 +
|platform= <!-- Compatible platforms - e.g. Symbian^1 and later, Qt 4.6 and later -->
 +
|devicecompatability= <!-- Compatible devices e.g.: All* (must have internal GPS) -->
 +
|dependencies= <!-- Any other/external dependencies e.g.: Google Maps Api v1.0 -->
 +
|signing= <!-- Signing requirements - empty or one of: Self-Signed, DevCert, Manufacturer -->
 +
|capabilities= <!-- Capabilities required by the article/code example (e.g. Location, NetworkServices. -->
 +
|keywords= <!-- APIs, classes and methods (e.g. QSystemScreenSaver, QList, CBase -->
 +
|language= <!-- Language category code for non-English topics - e.g. Lang-Chinese -->
 +
|translated-by= <!-- [[User:XXXX]] -->
 +
|translated-from-title= <!-- Title only -->
 +
|translated-from-id= <!-- Id of translated revision -->
 +
|review-by= <!-- After re-review: [[User:username]] -->
 +
|review-timestamp= <!-- After re-review: YYYYMMDD -->
 +
|update-by= <!-- After significant update: [[User:username]]-->
 +
|update-timestamp= <!-- After significant update: YYYYMMDD -->
 +
|creationdate= 20100226
 +
|author= [[User:Vivainio]]
 +
}}
  
 
==Introduction==
 
==Introduction==
  
D-Bus has become the de-facto standard way of doing RPC on Linux desktop (and
+
D-Bus has become the de-facto standard way of doing RPC on Linux desktop (and Maemo). Luckily, through {{Qapiname|QtDbus}}, it's also easy to use. This is a brief and "to-the-point" crash course on using QtDbus "the right way", i.e. through adapters and stubs. Simpler ways exist if you just need to call a random method on a random existing service somewhere, but for implementing "servers" on dbus the way presented here is the most robust one.
Maemo). Luckily, through QtDbus, it's also easy to use. This is a brief and
+
"to-the-point" crash course on using QtDbus "the right way", i.e. through
+
adaptors and stubs. Simpler ways exist if you just need to call a random method
+
on a random existing service somewhere, but for implementing "servers" on dbus
+
the way presented here is the most robust one.
+
  
 
The approach should be familiar to everyone that has played with CORBA (IDL files).
 
The approach should be familiar to everyone that has played with CORBA (IDL files).
Line 16: Line 33:
 
First, acquire or create the D-Bus ''interface xml file'' that describes the API.
 
First, acquire or create the D-Bus ''interface xml file'' that describes the API.
  
You can see lots of sample interface files in ''/usr/share/dbus-1/interfaces''
+
You can see lots of sample interface files in ''/usr/share/dbus-1/interfaces'' directory on your Linux desktop.
directory on your Linux desktop.
+
  
 
For purposes of this discussion, I'll be using a short xml file like this:
 
For purposes of this discussion, I'll be using a short xml file like this:
  
'''com.nokia.Demo.interface'''
+
'''com.nokia.Demo.xml'''
  
 
<code xml>  
 
<code xml>  
Line 35: Line 51:
 
         <arg name="eventkind" type="s" direction="out"/>
 
         <arg name="eventkind" type="s" direction="out"/>
 
     </signal>
 
     </signal>
 +
</interface>
 
</node>
 
</node>
 
</code>
 
</code>
  
It's not the simplest possible one, since it demonstrates a nice feature of
+
It's not the simplest possible one, since it demonstrates a nice feature of being able to pass {{Qapiname|QVariantMap}} in the arguments (type "''a{sv}''"). Sooner or later you'll want to use it, since it allows extending the functionality of a method without augmenting the interface (and as such supporting "loose typing").
being able to pass QVariantMap in the arguments (type "''a{sv}''"). Sooner or
+
later you'll want to use it, since it allows extending the functionality of a
+
method without augmenting the interface (and as such supporting "loose typing").
+
 
+
  
 
From this file, we'll create both client and server stubs:
 
From this file, we'll create both client and server stubs:
  
 
Client '''proxy''':
 
Client '''proxy''':
   
 
 
<code>     
 
<code>     
 
$ qdbusxml2cpp -v -c DemoIf -p demoif.h:demoif.cpp com.nokia.Demo.xml
 
$ qdbusxml2cpp -v -c DemoIf -p demoif.h:demoif.cpp com.nokia.Demo.xml
Line 53: Line 65:
 
    
 
    
 
Server stub ('''adaptor'''):
 
Server stub ('''adaptor'''):
 
 
<code>     
 
<code>     
 
$ qdbusxml2cpp -c DemoIfAdaptor -a demoifadaptor.h:demoifadaptor.cpp com.nokia.Demo.xml
 
$ qdbusxml2cpp -c DemoIfAdaptor -a demoifadaptor.h:demoifadaptor.cpp com.nokia.Demo.xml
 
</code>
 
</code>
  
You may want to store these commands in a script - it's not meant to be invoked
+
You may want to store these commands in a script - it's not meant to be invoked automatically, just add the generated files to version control and invoke the script when you edit the interface.
automatically, just add the generated files to version control and invoke the
+
script when you edit the interface.
+
  
 
==Implementing the server==
 
==Implementing the server==
  
The only nontrivial part is implementing the server. Create a project, add
+
The only nontrivial part is implementing the server. Create a project, add ''demoifadaptor.cpp'' to it, and take a look at ''demoifadaptor.h''. We'll create a new class ''MyDemo'' to implement the functionality (I'm intentionally using a name as tacky as MyDemo - normally I would call it DemoService, but now I'm trying to convey there is nothing magical about this class, apart from the fact that it inherits from {{Qapiname|QObject}}).
''demoifadaptor.cpp'' to it, and take a look at ''demoifadaptor.h''. We'll
+
create a new class ''MyDemo'' to implement the functionality (I'm intentionally
+
using a name as tacky as MyDemo - normally I would call it DemoService, but now
+
I'm trying to convey there is nothing magical about this class, apart from the
+
fact that it inherits from QObject).
+
  
Now the important part - you need to copy-paste the methods from DemoIfAdaptor
+
Now the important part - you need to copy-paste the methods from DemoIfAdaptor class to MyDemo, '''exactly''' as they appear in demoifadaptor.h. After this, mydemo.h looks like this:
class to MyDemo, '''exactly''' as they appear in demoifadaptor.h. After this,
+
mydemo.h looks like this:
+
  
<code cpp>
+
<code cpp-qt>
 
class MyDemo : public QObject
 
class MyDemo : public QObject
 
{
 
{
Line 91: Line 93:
 
</code>
 
</code>
  
The QObject magic used by the adaptor requires that the signatures in MyDemo
+
The QObject magic used by the adaptor requires that the signatures in MyDemo match the ones in the adaptor.
match the ones in the adaptor.
+
  
Note how Qt camelCase convention is not followed in the adaptor. This can help
+
Note how Qt camelCase convention is not followed in the adaptor. This can help to serve as a clue about whether we are dealing with vanilla Qt methods or the slightly "magical" dbus entry points.
to serve as a clue about whether we are dealing with vanilla Qt methods or the
+
slightly "magical" dbus entry points.
+
  
Now, in our main(), we hook the adaptor and our MyDemo class together:
+
Now, in our {{Icode|main()}}, we hook the adaptor and our MyDemo class together:
  
<code cpp>
+
<code cpp-qt>
 
int main(int argc, char *argv[])
 
int main(int argc, char *argv[])
 
{
 
{
Line 117: Line 116:
 
Note how we pass the MyDemo object as an argument to the constructor of the adaptor:
 
Note how we pass the MyDemo object as an argument to the constructor of the adaptor:
  
<code cpp>
+
<code cpp-qt>
 
new DemoIfAdaptor(demo);
 
new DemoIfAdaptor(demo);
 
</code>
 
</code>
  
This invocation sets ''demo'' object as QObject ''parent'' of the adaptor. This
+
This invocation sets ''demo'' object as {{Icode|QObject}} ''parent'' of the adaptor. This is very important, as it is the route adaptor uses to relay method calls to our {{Icode|MyDemo}} object! Also, when a signal ''LateEvent'' is emitted by {{Icode|MyDemo}}, the signal is magically broadcast to dbus by the adaptor (i.e. a ''Qt signal'' is converted to ''dbus signal'').
is very important, as it is the route adaptor uses to relay method calls to our
+
MyDemo object! Also, when a signal ''LateEvent'' is emitted by MyDemo, the
+
signal is magically broadcast to dbus by the adaptor (i.e. a ''Qt signal'' is
+
converted to ''dbus signal'').
+
  
Here, we use "/" as the '''object path''' visible to dbus. Prevalent convention
+
Here, we use "/" as the '''object path''' visible to dbus. Prevalent convention in case there is only one object is to use "/com/nokia/Demo", but this usually confuses newcomers (creating the illusion that the object path has to be a translated version of service / interface name).
in case there is only one object is to use "/com/nokia/Demo", but this usually
+
confuses newcomers (creating the illusion that the object path has to be a
+
translated version of service / interface name).
+
  
Obviously, the program needs to be started before the object is visible on the
+
Obviously, the program needs to be started before the object is visible on the D-Bus. For now, you can do this manually from the command line. In practice, D-Bus services should have an entry in ''/usr/share/dbus-1/services'', where dbus-daemon will find it and be able to launch the service when a call is done (so called "'''dbus activation'''"). See [[Qt application for Maemo with DBus support]] for an example.
D-Bus. For now, you can do this manually from the command line. In practice,
+
D-Bus services should have an entry in ''/usr/share/dbus-1/services'', where
+
dbus-daemon will find it and be able to launch the service when a call is done
+
(so called "'''dbus activation'''"). See
+
[[Qt_application_for_Maemo_with_DBus_support]] for an example.
+
  
Let's see that our object is available after launch by using "qdbus" inspection
+
Let's see that our object is available after launch by using "qdbus" inspection tool:
tool:
+
  
 
<code>
 
<code>
Line 157: Line 143:
 
</code>
 
</code>
  
As you can see, QtDbus adds lots of extra functionality for free.
+
or
 +
 
 +
<code>
 +
$ dbus-send --type=method_call --print-reply \
 +
    --dest=com.nokia.Demo / org.freedesktop.DBus.Introspectable.Introspect
 +
</code>
 +
 
 +
As you can see, {{Icode|QtDbus}} adds lots of extra functionality for free.
  
 
We can make it say "bye" by invoking:
 
We can make it say "bye" by invoking:
Line 165: Line 158:
 
</code>
 
</code>
  
(qdbus is also available on the N900 device if you install "qqdbus", currently
+
or
packaged in fremantle extras-devel).
+
 
 +
<code>
 +
$ dbus-send --type=method_call --print-reply \
 +
    --dest=com.nokia.Demo / com.nokia.Demo.SayBye
 +
</code>
 +
 
 +
(qdbus is also available on the N900 device if you install "qqdbus", currently packaged in fremantle extras-devel. If you can tolerate a slightly more verbose syntax, you can use dbus-send tool, installed on the device by default).
  
 
==Using the service==
 
==Using the service==
  
Using the client proxy is trivial - just include the generated "demoif.h",
+
Using the client proxy is trivial - just include the generated "demoif.h", instantiate DemoIf (specifying the service and object path), connect to signals you are interested in and call the methods:
instantiate DemoIf (specifying the service and object path), connect to signals
+
you are interested in and call the methods:
+
  
<code cpp>
+
<code cpp-qt>
 
DemoIf* client = new DemoIf("com.nokia.Demo", "/", QDBusConnection::sessionBus(), 0);
 
DemoIf* client = new DemoIf("com.nokia.Demo", "/", QDBusConnection::sessionBus(), 0);
 
QObject::connect(client, SIGNAL(LateEvent(QString)), this, SLOT(mySlot(QString)));     
 
QObject::connect(client, SIGNAL(LateEvent(QString)), this, SLOT(mySlot(QString)));     
Line 180: Line 177:
 
</code>
 
</code>
  
Optionally, you may use the fully qualified name ''com::nokia::DemoIf'' to refer
+
Optionally, you may use the fully qualified name ''com::nokia::DemoIf'' to refer to the {{Icode|DemoIf}} class.
to the DemoIf class.
+
 
 +
==Project Configuration==
 +
 
 +
Add
 +
<code>
 +
QT += dbus
 +
</code>
 +
 
 +
to your .pro file
 +
 
 +
more info: http://doc-snapshot.qt-project.org/4.8/qtdbus.html
  
 
=== Recommended links ===
 
=== Recommended links ===
  
* [http://qt.nokia.com/doc/qtdbus.html QtDbus official documentation] (includes introduction to D-Bus in general)
+
* [http://doc.qt.nokia.com/4.7/qtdbus.html QtDbus official documentation] (includes [http://doc.qt.nokia.com/4.7/intro-to-dbus.html introduction] to D-Bus in general)
 
* [http://techbase.kde.org/Development/Tutorials Tutorials at KDE TechBase]
 
* [http://techbase.kde.org/Development/Tutorials Tutorials at KDE TechBase]
 
* [http://www.freedesktop.org/wiki/Software/dbus The official D-Bus page af freedesktop.org]
 
* [http://www.freedesktop.org/wiki/Software/dbus The official D-Bus page af freedesktop.org]

Latest revision as of 04:18, 11 October 2012

Article Metadata
Article
Created: vivainio (26 Feb 2010)
Last edited: hamishwillee (11 Oct 2012)

Contents

[edit] Introduction

D-Bus has become the de-facto standard way of doing RPC on Linux desktop (and Maemo). Luckily, through QtDbus, it's also easy to use. This is a brief and "to-the-point" crash course on using QtDbus "the right way", i.e. through adapters and stubs. Simpler ways exist if you just need to call a random method on a random existing service somewhere, but for implementing "servers" on dbus the way presented here is the most robust one.

The approach should be familiar to everyone that has played with CORBA (IDL files).

[edit] Stubs

First, acquire or create the D-Bus interface xml file that describes the API.

You can see lots of sample interface files in /usr/share/dbus-1/interfaces directory on your Linux desktop.

For purposes of this discussion, I'll be using a short xml file like this:

com.nokia.Demo.xml

<node>
<interface name="com.nokia.Demo">
<method name="SayHello">
<annotation name="com.trolltech.QtDBus.QtTypeName.In1" value="QVariantMap"/>
<arg name="name" type="s" direction="in" />
<arg name="customdata" type="a{sv}" direction="in" />
</method>
<method name="SayBye"/>
<signal name="LateEvent">
<arg name="eventkind" type="s" direction="out"/>
</signal>
</interface>
</node>

It's not the simplest possible one, since it demonstrates a nice feature of being able to pass QVariantMap in the arguments (type "a{sv}"). Sooner or later you'll want to use it, since it allows extending the functionality of a method without augmenting the interface (and as such supporting "loose typing").

From this file, we'll create both client and server stubs:

Client proxy:

$ qdbusxml2cpp -v -c DemoIf -p demoif.h:demoif.cpp com.nokia.Demo.xml

Server stub (adaptor):

$ qdbusxml2cpp -c DemoIfAdaptor -a demoifadaptor.h:demoifadaptor.cpp com.nokia.Demo.xml

You may want to store these commands in a script - it's not meant to be invoked automatically, just add the generated files to version control and invoke the script when you edit the interface.

[edit] Implementing the server

The only nontrivial part is implementing the server. Create a project, add demoifadaptor.cpp to it, and take a look at demoifadaptor.h. We'll create a new class MyDemo to implement the functionality (I'm intentionally using a name as tacky as MyDemo - normally I would call it DemoService, but now I'm trying to convey there is nothing magical about this class, apart from the fact that it inherits from QObject).

Now the important part - you need to copy-paste the methods from DemoIfAdaptor class to MyDemo, exactly as they appear in demoifadaptor.h. After this, mydemo.h looks like this:

class MyDemo : public QObject
{
Q_OBJECT
public:
explicit MyDemo(QObject *parent = 0);
 
public Q_SLOTS:
void SayBye();
void SayHello(const QString &name, const QVariantMap &customdata);
Q_SIGNALS:
void LateEvent(const QString &eventkind);
 
};

The QObject magic used by the adaptor requires that the signatures in MyDemo match the ones in the adaptor.

Note how Qt camelCase convention is not followed in the adaptor. This can help to serve as a clue about whether we are dealing with vanilla Qt methods or the slightly "magical" dbus entry points.

Now, in our main(), we hook the adaptor and our MyDemo class together:

int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
 
MyDemo* demo = new MyDemo;
new DemoIfAdaptor(demo);
 
QDBusConnection connection = QDBusConnection::sessionBus();
bool ret = connection.registerService("com.nokia.Demo");
ret = connection.registerObject("/", demo);
return a.exec();
}

Note how we pass the MyDemo object as an argument to the constructor of the adaptor:

new DemoIfAdaptor(demo);

This invocation sets demo object as QObject parent of the adaptor. This is very important, as it is the route adaptor uses to relay method calls to our MyDemo object! Also, when a signal LateEvent is emitted by MyDemo, the signal is magically broadcast to dbus by the adaptor (i.e. a Qt signal is converted to dbus signal).

Here, we use "/" as the object path visible to dbus. Prevalent convention in case there is only one object is to use "/com/nokia/Demo", but this usually confuses newcomers (creating the illusion that the object path has to be a translated version of service / interface name).

Obviously, the program needs to be started before the object is visible on the D-Bus. For now, you can do this manually from the command line. In practice, D-Bus services should have an entry in /usr/share/dbus-1/services, where dbus-daemon will find it and be able to launch the service when a call is done (so called "dbus activation"). See Qt application for Maemo with DBus support for an example.

Let's see that our object is available after launch by using "qdbus" inspection tool:

$ qdbus com.nokia.Demo 
/
 
$ qdbus com.nokia.Demo /
signal void com.nokia.Demo.LateEvent(QString eventkind)
method void com.nokia.Demo.SayBye()
method void com.nokia.Demo.SayHello(QString name, QVariantMap customdata)
method QDBusVariant org.freedesktop.DBus.Properties.Get(QString interface_name, QString property_name)
method QVariantMap org.freedesktop.DBus.Properties.GetAll(QString interface_name)
method void org.freedesktop.DBus.Properties.Set(QString interface_name, QString property_name, QDBusVariant value)
method QString org.freedesktop.DBus.Introspectable.Introspect()

or

$ dbus-send --type=method_call --print-reply \
--dest=com.nokia.Demo / org.freedesktop.DBus.Introspectable.Introspect

As you can see, QtDbus adds lots of extra functionality for free.

We can make it say "bye" by invoking:

$ qdbus com.nokia.Demo / com.nokia.Demo.SayBye

or

$ dbus-send --type=method_call --print-reply \
--dest=com.nokia.Demo / com.nokia.Demo.SayBye

(qdbus is also available on the N900 device if you install "qqdbus", currently packaged in fremantle extras-devel. If you can tolerate a slightly more verbose syntax, you can use dbus-send tool, installed on the device by default).

[edit] Using the service

Using the client proxy is trivial - just include the generated "demoif.h", instantiate DemoIf (specifying the service and object path), connect to signals you are interested in and call the methods:

DemoIf* client = new DemoIf("com.nokia.Demo", "/", QDBusConnection::sessionBus(), 0);
QObject::connect(client, SIGNAL(LateEvent(QString)), this, SLOT(mySlot(QString)));
client->SayBye();

Optionally, you may use the fully qualified name com::nokia::DemoIf to refer to the DemoIf class.

[edit] Project Configuration

Add

QT += dbus

to your .pro file

more info: http://doc-snapshot.qt-project.org/4.8/qtdbus.html

[edit] Recommended links

This page was last modified on 11 October 2012, at 04:18.
647 page views in the last 30 days.
×