×
Namespaces

Variants
Actions
(Difference between revisions)

QtDbus quick tutorial

From Nokia Developer Wiki
Jump to: navigation, search
vivainio (Talk | contribs)
vivainio (Talk | contribs)
Line 3: Line 3:
 
==Introduction==
 
==Introduction==
  
DBus has become the de-facto standard way of doing RPC on Linux desktop (and Maemo). Luckily,  
+
D-Bus has become the de-facto standard way of doing RPC on Linux desktop (and
through QtDbus, it's also easy to use. This is a brief and "to-the-point" crash  
+
Maemo). Luckily, through QtDbus, it's also easy to use. This is a brief and
course on using QtDbus "the right way", i.e. through adaptors and stubs. Simpler
+
"to-the-point" crash course on using QtDbus "the right way", i.e. through
ways exist if you just need to call a random method on a random existing service somewhere,
+
adaptors and stubs. Simpler ways exist if you just need to call a random method
but for implementing "servers" on dbus the way presented here is the most robust one.
+
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 13: Line 14:
 
==Stubs==
 
==Stubs==
  
First, acquire or create the dbus ''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'' directory on your Linux desktop.
+
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:
 
For purposes of this discussion, I'll be using a short xml file like this:
Line 22: Line 24:
  
 
<code xml>  
 
<code xml>  
 
 
<node>
 
<node>
 
   <interface name="com.nokia.Demo">
 
   <interface name="com.nokia.Demo">
Line 35: Line 36:
 
     </signal>
 
     </signal>
 
</node>
 
</node>
 
 
</code>
 
</code>
  
It's not the simplest possible one, since it demonstrates a nice feature of being able to
+
It's not the simplest possible one, since it demonstrates a nice feature of
pass QVariantMap in the arguments (type "''a{sv}''"). Sooner or later you'll want to use it, since it allows
+
being able to pass QVariantMap in the arguments (type "''a{sv}''"). Sooner or
extending the functionality of a method without augmenting the interface (and as such
+
later you'll want to use it, since it allows extending the functionality of a
supporting "loose typing").
+
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 stub ('''proxy'''):
+
Client '''proxy''':
 
      
 
      
 
<code>     
 
<code>     
Line 58: Line 58:
 
</code>
 
</code>
  
You may want to store these commands in a script - it's not meant to be invoked automatically, just  
+
You may want to store these commands in a script - it's not meant to be invoked
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 ''demoifadaptor.cpp'' to it,
+
The only nontrivial part is implementing the server. Create a project, add
and take a look at ''demoifadaptor.h''. We'll create a new class ''MyDemo'' to implement the functionality  
+
''demoifadaptor.cpp'' to it, and take a look at ''demoifadaptor.h''. We'll
(I'm intentionally using a name as tacky as MyDemo - normally I would call it DemoService, but
+
create a new class ''MyDemo'' to implement the functionality (I'm intentionally
now I'm trying to convey there is nothing magical about this class, apart from the fact that it
+
using a name as tacky as MyDemo - normally I would call it DemoService, but now
inherits from QObject).
+
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'''
+
Now the important part - you need to copy-paste the methods from DemoIfAdaptor
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>
Line 79: Line 82:
 
     explicit MyDemo(QObject *parent = 0);
 
     explicit MyDemo(QObject *parent = 0);
  
public Q_SLOTS: // METHODS
+
public Q_SLOTS:
 
     void SayBye();
 
     void SayBye();
 
     void SayHello(const QString &name, const QVariantMap &customdata);
 
     void SayHello(const QString &name, const QVariantMap &customdata);
Q_SIGNALS: // SIGNALS
+
Q_SIGNALS:
 
     void LateEvent(const QString &eventkind);
 
     void LateEvent(const QString &eventkind);
  
 
};
 
};
 
 
</code>
 
</code>
  
The QObject magic used by the adaptor requires that the signatures in MyDemo match the ones
+
The QObject magic used by the adaptor requires that the signatures in MyDemo
in the adaptor.
+
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
+
Note how Qt camelCase convention is not followed in the adaptor. This can help
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 main(), we hook the adaptor and our MyDemo class together:
 
  
 
<code cpp>
 
<code cpp>
Line 119: Line 121:
 
</code>
 
</code>
  
This invocation sets ''demo'' object as QObject ''parent'' of the adaptor. This is very important, as it is
+
This invocation sets ''demo'' object as QObject ''parent'' of the adaptor. This
the route adaptor uses to relay method calls to our MyDemo object! Also, when  
+
is very important, as it is the route adaptor uses to relay method calls to our
a signal ''LateEvent'' is emitted by MyDemo, the signal is magically broadcast to dbus by the adaptor (i.e. a ''Qt signal''
+
MyDemo object! Also, when a signal ''LateEvent'' is emitted by MyDemo, the
is converted to ''dbus signal'').
+
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  
+
Here, we use "/" as the '''object path''' visible to dbus. Prevalent convention
to use "/com/nokia/Demo", but this usually confuses newcomers (creating the illusion that the object path
+
in case there is only one object is to use "/com/nokia/Demo", but this usually
has to be a translated version of service / interface name).
+
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 dbus. For now, you can do
+
Obviously, the program needs to be started before the object is visible on the
this manually from the command line. In practice, dbus services should have an entry in ''/usr/share/dbus-1/services'',
+
D-Bus. For now, you can do this manually from the command line. In practice,
where dbus-daemon will find it and be able to launch the service when a call is done (so called "'''dbus activation'''"). See
+
D-Bus services should have an entry in ''/usr/share/dbus-1/services'', where
http://wiki.forum.nokia.com/index.php/Qt_application_for_Maemo_with_DBus_support for an example.
+
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:
+
Let's see that our object is available after launch by using "qdbus" inspection
 
+
tool:
----
+
  
 
<code>
 
<code>
Line 151: Line 156:
  
 
</code>
 
</code>
 
  
 
As you can see, QtDbus adds lots of extra functionality for free.
 
As you can see, 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 162: Line 165:
 
</code>
 
</code>
  
(qdbus is also available on the N900 device if you install "qqdbus", currently packaged in fremantle extras-devel).  
+
(qdbus is also available on the N900 device if you install "qqdbus", currently
 +
packaged in fremantle extras-devel).
  
 
==Using the service==
 
==Using the service==
  
Using the client proxy is trivial - just include the generated "demoif.h", instantiate DemoIf (specifying the service and object path),  
+
Using the client proxy is trivial - just include the generated "demoif.h",
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>
DemoIf* client = new DemoIf("com.nokia.Demo", "/", QDBusConnection::sessionBus(), NULL);
+
DemoIf* client = new DemoIf("com.nokia.Demo", "/", QDBusConnection::sessionBus(), 0);
QObject::connect(client, SIGNAL(LateEvent(QString)), this, SLOT(handleEvent(QString)));     
+
QObject::connect(client, SIGNAL(LateEvent(QString)), this, SLOT(mySlot(QString)));     
 
client->SayBye();
 
client->SayBye();
 
</code>
 
</code>
  
Optionally, you may use the fully qualified name ''com::nokia::DemoIf'' to refer to the DemoIf class.
+
Optionally, you may use the fully qualified name ''com::nokia::DemoIf'' to refer
 +
to the DemoIf class.
 +
 
 +
=== Recommended links ===
 +
 
 +
* [http://qt.nokia.com/doc/qtdbus.html QtDbus official documentation] (includes introduction to D-Bus in general)
 +
* [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]

Revision as of 11:38, 26 February 2010


Contents

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

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

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

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()

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

(qdbus is also available on the N900 device if you install "qqdbus", currently packaged in fremantle extras-devel).

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.

Recommended links

704 page views in the last 30 days.