D-Bus Tutorial

At work, we use D-Bus to communicate from one process to another. However, when looking for information on how exactly to use D-Bus, the documentation that I came across was rather useless. So, here’s a tutorial on how to use D-Bus using both updated Java bindings, and C++ bindings using dbus-cxx.

The source code for this tutorial can be found on github.  As there is a lot of boilerplate code, the examples shown in this tutorial are not fully commented, due to a large amount of boilerplate code.

Part I: Getting what you need

For this, you’re going to need a few things. First, make sure that you have the dbus development headers installed. You can check this by using synaptic or whatever package manager you use.

Next, make sure that you have dbus-cxx.  There are directions at the dbus-cxx website on how to build. The examples on the dbus-cxx website are fairly useful, so that’s a good place to go for more help.

For Java, the easiest way is to use Maven to build the projects, as the updated DBus implementation is on Maven Central.  In order to use the version from Maven Central, the following should be added to your pom.xml:

<dependency>
    <groupId>com.github.hypfvieh</groupId>
    <artifactId>dbus-java</artifactId>
    <version>3.2.4</version>
</dependency>

I recommend that you also install qdbus. qdbus allows you to send out events and method calls on the D-Bus. On Debian / Ubuntu based systems, qdbus can be installed by doing:

apt-get install qdbus-qt5

Other useful utilities are dbus-monitor and dbus-send, which should be installed as part of the standard D-Bus installation.

Part II: Some Background

Let’s talk a little bit about what D-Bus is. In short, D-Bus is an IPC(Inter-Process Communication) and RPC(Remote Procedure Call) mechanism. This means that you use D-Bus to talk between processes. There are several other ways of communicating between processes, such as using a TCP connection between processes. On *nix based systems, you could also create a pipe to link two processes together. Shared memory is another option. Or, you could send signals from one program to another.

All of these options have drawbacks and advantages. D-Bus takes a slightly different route, where all messages are sent through a central bus system and then forwarded on to the proper recipients, similar to how an IP packet works.

Part III: Looking at what is on the D-Bus

This part uses qdbus to inspect what services are currently on the D-Bus. Let’s open up dbus-monitor in one terminal. Now, in another terminal run qdbus. Here’s the output that I got with qdbus on a Debian 10 system:

user@machine:~$ qdbus
:1.0
 org.freedesktop.systemd1
:1.1
:1.10
 org.freedesktop.Notifications
 org.freedesktop.impl.portal.desktop.gnome
 org.gnome.Magnifier
 org.gnome.Mutter.DisplayConfig
 org.gnome.Mutter.IdleMonitor
 org.gnome.Mutter.RemoteDesktop
 org.gnome.Mutter.ScreenCast
 org.gnome.Panel
 org.gnome.ScreenSaver
 org.gnome.Shell
 org.gnome.Shell.AudioDeviceSelection
 org.gnome.Shell.Screencast
 org.gnome.Shell.Screenshot
 org.gnome.Shell.Wacom.PadOsd
 org.gnome.keyring.SystemPrompter
 org.gtk.MountOperationHandler
 org.gtk.Notifications
:1.11
 org.gtk.vfs.Daemon
:1.12
:1.13
:1.14
 org.a11y.Bus
:1.15
:1.16
 org.PulseAudio1
 org.pulseaudio.Server
:1.17
 org.gnome.Shell.CalendarServer
:1.18
 org.gnome.evolution.dataserver.Sources5
:1.19
 org.freedesktop.Telepathy.Client.GnomeShell._3a1_2e19.n0
:1.20
 org.freedesktop.Telepathy.AccountManager
 org.freedesktop.Telepathy.ChannelDispatcher
 org.freedesktop.Telepathy.MissionControl5
:1.21
:1.22
 org.gtk.vfs.UDisks2VolumeMonitor
:1.23
 org.gtk.vfs.GoaVolumeMonitor
:1.24
 org.gnome.OnlineAccounts
:1.25
 org.gnome.Identity
:1.26
 org.gtk.vfs.GPhoto2VolumeMonitor
:1.27
 org.gtk.vfs.MTPVolumeMonitor
:1.28
 org.gtk.vfs.AfcVolumeMonitor
:1.29
:1.30
 org.gnome.SettingsDaemon.Rfkill
:1.31
 org.freedesktop.ScreenSaver
:1.32
 org.gnome.SettingsDaemon.Smartcard
:1.33
 org.gnome.SettingsDaemon.Sharing
:1.34
:1.35
 org.gnome.evolution.dataserver.Calendar7
:1.36
:1.37
:1.38
 org.gnome.SettingsDaemon.Power
:1.39
:1.40
 org.gnome.SettingsDaemon.Color
:1.41
 org.gtk.Settings
:1.42
:1.43
 org.gnome.SettingsDaemon.Wacom
:1.430
:1.44
 org.gnome.SettingsDaemon.Housekeeping
:1.45
:1.46
 org.gnome.SettingsDaemon.MediaKeys
:1.464
 org.freedesktop.FileManager1
 org.gnome.Nautilus
:1.47
:1.48
 ca.desrt.dconf
:1.49
 org.gnome.evolution.dataserver.AddressBook9
:1.502
:1.51
 org.freedesktop.Tracker1
:1.52
 org.gnome.Disks.NotificationMonitor
:1.53
 org.freedesktop.Tracker1.Miner.Files
:1.54
 org.freedesktop.PackageKit
 org.gnome.Software
:1.543
:1.544
:1.56
 org.freedesktop.Tracker1.Miner.Applications
:1.57
 org.gnome.Evolution-alarm-notify
:1.58
:1.62
 org.gnome.seahorse.Application
:1.63
 org.gnome.Terminal
:1.66
 org.gtk.vfs.mountpoint_1977
:1.74
 org.gtk.vfs.mountpoint_2086
:1.78
 org.gtk.vfs.Metadata
:1.8
 org.gnome.SessionManager
:1.82
 org.gtk.vfs.mountpoint_35377
:1.87
 org.gtk.vfs.mountpoint_dnssd
:1.9
 org.freedesktop.secrets
 org.gnome.keyring
:1.94
:1.95
:1.96
:1.98
:1.99
org.freedesktop.DBus

Your output may differ. If you look at the terminal with dbus-monitor running in it, you’ll notice how there was a lot of output. Those are messages that are being sent over the bus. In fact, there’s too much to really go over now, so we’ll come back to that at a later point.

The output from qdbus is a little more sensible. The services are identified by their names, i.e. org.freedesktop.Notifications. These are the bus names(note that these names must be unique). Now, let’s look at a specific service, in this case org.freedesktop.Notifications.
Here’s the output that I get(note: entries from other paths on this same path for clarity):

user@machine:~$ qdbus org.freedesktop.Notifications 
 /
 /org
 /org/freedesktop
 /org/freedesktop/Notifications

The output that you get here are the paths. Now, let’s investigate the path /org/freedesktop/Notifications.

user@machine:~$ qdbus org.freedesktop.Notifications /org/freedesktop/Notifications 
signal void org.freedesktop.DBus.Properties.PropertiesChanged(QString interface_name, QVariantMap changed_properties, QStringList invalidated_properties)
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()
method QString org.freedesktop.DBus.Peer.GetMachineId()
method void org.freedesktop.DBus.Peer.Ping()
signal void org.freedesktop.Notifications.ActionInvoked(uint arg_0, QString arg_1)
signal void org.freedesktop.Notifications.NotificationClosed(uint arg_0, uint arg_1)
method void org.freedesktop.Notifications.CloseNotification(uint arg_0)
method QStringList org.freedesktop.Notifications.GetCapabilities()
method QString org.freedesktop.Notifications.GetServerInformation(QString& arg_1, QString& arg_2, QString& arg_3)
method uint org.freedesktop.Notifications.Notify(QString arg_0, uint arg_1, QString arg_2, QString arg_3, QString arg_4, QStringList arg_5, QVariantMap arg_6, int arg_7)

As you can see, there are a few methods on here. NOTE: Not all of the methods here are shown. I am not sure why, but I believe that it is because qdbus cannot send to all of these methods.

Notice how one of the methods that is shown is called Introspect. All objects that are on the D-Bus must implement this method. When called, it returns an XML string defining what methods are on this object at this path. So, let’s try calling the Introspect method.

user@machine:~$ qdbus org.freedesktop.Notifications /org/freedesktop/Notifications org.freedesktop.DBus.Introspectable.Introspect 
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
                      "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<!-- GDBus 2.58.3 -->
<node>
  <interface name="org.freedesktop.DBus.Properties">
    <method name="Get">
      <arg type="s" name="interface_name" direction="in"/>
      <arg type="s" name="property_name" direction="in"/>
      <arg type="v" name="value" direction="out"/>
    </method>
    <method name="GetAll">
      <arg type="s" name="interface_name" direction="in"/>
      <arg type="a{sv}" name="properties" direction="out"/>
    </method>
    <method name="Set">
      <arg type="s" name="interface_name" direction="in"/>
      <arg type="s" name="property_name" direction="in"/>
      <arg type="v" name="value" direction="in"/>
    </method>
    <signal name="PropertiesChanged">
      <arg type="s" name="interface_name"/>
      <arg type="a{sv}" name="changed_properties"/>
      <arg type="as" name="invalidated_properties"/>
    </signal>
  </interface>
  <interface name="org.freedesktop.DBus.Introspectable">
    <method name="Introspect">
      <arg type="s" name="xml_data" direction="out"/>
    </method>
  </interface>
  <interface name="org.freedesktop.DBus.Peer">
    <method name="Ping"/>
    <method name="GetMachineId">
      <arg type="s" name="machine_uuid" direction="out"/>
    </method>
  </interface>
  <interface name="org.freedesktop.Notifications">
    <method name="Notify">
      <arg type="s" name="arg_0" direction="in">
      </arg>
      <arg type="u" name="arg_1" direction="in">
      </arg>
      <arg type="s" name="arg_2" direction="in">
      </arg>
      <arg type="s" name="arg_3" direction="in">
      </arg>
      <arg type="s" name="arg_4" direction="in">
      </arg>
      <arg type="as" name="arg_5" direction="in">
      </arg>
      <arg type="a{sv}" name="arg_6" direction="in">
      </arg>
      <arg type="i" name="arg_7" direction="in">
      </arg>
      <arg type="u" name="arg_8" direction="out">
      </arg>
    </method>
    <method name="CloseNotification">
      <arg type="u" name="arg_0" direction="in">
      </arg>
    </method>
    <method name="GetCapabilities">
      <arg type="as" name="arg_0" direction="out">
      </arg>
    </method>
    <method name="GetServerInformation">
      <arg type="s" name="arg_0" direction="out">
      </arg>
      <arg type="s" name="arg_1" direction="out">
      </arg>
      <arg type="s" name="arg_2" direction="out">
      </arg>
      <arg type="s" name="arg_3" direction="out">
      </arg>
    </method>
    <signal name="NotificationClosed">
      <arg type="u" name="arg_0">
      </arg>
      <arg type="u" name="arg_1">
      </arg>
    </signal>
    <signal name="ActionInvoked">
      <arg type="u" name="arg_0">
      </arg>
      <arg type="s" name="arg_1">
      </arg>
    </signal>
  </interface>
</node>

Notice how we now have a list of all methods that are on the D-Bus from the address org.freedesktop.Notifications and the path /org/freedesktop/Notifications. As you can see, there is also a new method called Notify. This method is what pops up the notification window in one corner of your screen. Depending on the version of the notification-daemon that you have installed, it may also allow you to click on it.

dbus-cxx and dbus-java both have tools that help you make adapters from these XML documents. However, it’s also possible to make interfaces on your own, so we will not bother with the tools, as I think that it will make more sense to you if you don’t start out with the tools.

So at this point, we now have a basic understanding of what is on the D-Bus, and how we can see what is on there. Let’s go and make some programs that talk to each other.

Part IV: A Basic Java program(Receiving)

Let’s start with receiving messages first. For Java, any method that you want to export onto the D-Bus must be defined in an interface which extends DBusInterface. Here’s a simple interface, which will echo out whatever string we give it and return an int.

package com.rm5248;

import org.freedesktop.dbus.interfaces.DBusInterface;

public interface ReceiveInterface extends DBusInterface {

    public int echoMessage(String str);

    public int echoAndAdd(String str, int a, int b);

}

Our main class is just JavaReceive, which implements ReceiveInterface. The implementation of this method simply prints the string to System.out, and returns 0. Now, let’s put this object onto the D-Bus and call it. This is actually quite simple. In fact, this is all the code that is required:

try {
    DBusConnection conn = DBusConnection.getConnection(DBusConnection.DBusBusType.SESSION);
    conn.requestBusName("com.rm5248");
    conn.exportObject("/", new JavaReceive());
} catch (DBusException e) {
    e.printStackTrace();
}

What does this code do? Well, the first part gets a new connection to the dbus on the session bus(there is another bus, the system bus, but for most purposes the session bus should be good). Once we have a connection, we request a name. This name could be really whatever we want, but generally you should use the Java package style. We now export an object onto the D-Bus. The first parameter is the path of the object. In the case of the notifications that we looked at before, this would be /org/freedesktop/Notifications. For simplicity, we are simply setting it to /. The second parameter is a class which implements an interface which extends the DBusInterface.

Now, let’s look on the D-Bus to see what we have.

user@machine:~/dbus-tutorial/dbus_java_receive$ qdbus
:1.0
 org.freedesktop.systemd1
:1.1
:1.10
 org.freedesktop.Notifications
 org.freedesktop.impl.portal.desktop.gnome
 org.gnome.Magnifier
 org.gnome.Mutter.DisplayConfig
 org.gnome.Mutter.IdleMonitor
 org.gnome.Mutter.RemoteDesktop
 org.gnome.Mutter.ScreenCast
 org.gnome.Panel
 org.gnome.ScreenSaver
 org.gnome.Shell
 org.gnome.Shell.AudioDeviceSelection
 org.gnome.Shell.Screencast
 org.gnome.Shell.Screenshot
 org.gnome.Shell.Wacom.PadOsd
 org.gnome.keyring.SystemPrompter
 org.gtk.MountOperationHandler
 org.gtk.Notifications
:1.11
 org.gtk.vfs.Daemon
:1.12
:1.13
:1.14
 org.a11y.Bus
:1.15
:1.16
 org.PulseAudio1
 org.pulseaudio.Server
:1.17
 org.gnome.Shell.CalendarServer
:1.18
 org.gnome.evolution.dataserver.Sources5
:1.19
 org.freedesktop.Telepathy.Client.GnomeShell._3a1_2e19.n0
:1.20
 org.freedesktop.Telepathy.AccountManager
 org.freedesktop.Telepathy.ChannelDispatcher
 org.freedesktop.Telepathy.MissionControl5
:1.21
:1.22
 org.gtk.vfs.UDisks2VolumeMonitor
:1.23
 org.gtk.vfs.GoaVolumeMonitor
:1.24
 org.gnome.OnlineAccounts
:1.25
 org.gnome.Identity
:1.26
 org.gtk.vfs.GPhoto2VolumeMonitor
:1.27
 org.gtk.vfs.MTPVolumeMonitor
:1.28
 org.gtk.vfs.AfcVolumeMonitor
:1.29
:1.30
 org.gnome.SettingsDaemon.Rfkill
:1.31
 org.freedesktop.ScreenSaver
:1.32
 org.gnome.SettingsDaemon.Smartcard
:1.33
 org.gnome.SettingsDaemon.Sharing
:1.34
:1.35
 org.gnome.evolution.dataserver.Calendar7
:1.36
:1.37
:1.38
 org.gnome.SettingsDaemon.Power
:1.39
:1.40
 org.gnome.SettingsDaemon.Color
:1.41
 org.gtk.Settings
:1.42
:1.43
 org.gnome.SettingsDaemon.Wacom
:1.430
:1.44
 org.gnome.SettingsDaemon.Housekeeping
:1.45
:1.46
 org.gnome.SettingsDaemon.MediaKeys
:1.464
 org.freedesktop.FileManager1
 org.gnome.Nautilus
:1.47
:1.48
 ca.desrt.dconf
:1.49
 org.gnome.evolution.dataserver.AddressBook9
:1.502
:1.51
 org.freedesktop.Tracker1
:1.52
 org.gnome.Disks.NotificationMonitor
:1.53
 org.freedesktop.Tracker1.Miner.Files
:1.54
 org.freedesktop.PackageKit
 org.gnome.Software
:1.543
:1.56
 org.freedesktop.Tracker1.Miner.Applications
:1.57
 org.gnome.Evolution-alarm-notify
:1.58
:1.598
 com.rm5248
:1.600
:1.62
 org.gnome.seahorse.Application
:1.63
 org.gnome.Terminal
:1.66
 org.gtk.vfs.mountpoint_1977
:1.74
 org.gtk.vfs.mountpoint_2086
:1.78
 org.gtk.vfs.Metadata
:1.8
 org.gnome.SessionManager
:1.82
 org.gtk.vfs.mountpoint_35377
:1.87
 org.gtk.vfs.mountpoint_dnssd
:1.9
 org.freedesktop.secrets
 org.gnome.keyring
:1.94
:1.95
:1.96
:1.98
:1.99
org.freedesktop.DBus

If you look, you’ll notice how we now have a new com.rm5248 entry. Let’s look at what qdbus says that entry contains:

user@machine:~$ qdbus com.rm5248 /
method int com.rm5248.ReceiveInterface.echoMessage(QString)
method QString org.freedesktop.DBus.Introspectable.Introspect()
method void org.freedesktop.DBus.Peer.Ping()

There is the standard Introspect method, as well as an echoMessage method that takes a QString(this is the same as a regular string, but qdbus changes all of the types to types that are in Qt).

Now, let’s call the echo method using qdbus.

user@machine:~$ qdbus com.rm5248 / com.rm5248.ReceiveInterface.echoMessage hello
0

If you look at the output for your Java program, notice how it has printed out the string ‘hello’! The ‘0’ that is printed underneath the qdbus call is the return of the method. Since we simply return 0, a 0 is printed.

Notice how we called the method. It’s full path is com.rm5248.ReceiveInterface.echoMessage. Note that this is the same as the Java package definition. It is not dependent on the bus name(com.rm5248). If we changed the bus name to foo.bar, we would call the method as such:

user@machine:~$ qdbus foo.bar / com.rm5248.ReceiveInterface.echoMessage hello

Now let’s add a new method to the interface. This new method will take in a string, two ints, and return the addition. Here’s the signature as defined in the interface:

public int echoAndAdd(String str, int a, int b);

Now if we look at it using qdbus, this is what it looks like:

method int com.rm5248.ReceiveInterface.echoAndAdd(QString, int, int)

We call it the same way we called the echoMessage method, just with more parameters.

user@machine:~$ qdbus com.rm5248 / com.rm5248.ReceiveInterface.echoAndAdd Adding 5 9
14

Notice that the Java program outputs “Adding”, and then returns the addition of 5 and 9(which is 14 and the return value). If you want to output a string with spaces, simply put the string inside of double quotes and qdbus will send it out. You can see these methods and the return information using dbus-monitor. Here is the output for a method call and return:

method call time=1612575073.159227 sender=:1.624 -> destination=com.rm5248 serial=5 path=/; interface=com.rm5248.ReceiveInterface; member=echoAndAdd
   string "Adding"
   int32 5
   int32 9
method return time=1612575073.159417 sender=:1.598 -> destination=:1.624 serial=41 reply_serial=5
   int32 14

qdbus will call the introspection information to call the right method, but this part of the output is the actual method call and return.

Now that we have made some methods for Java and called them with qdbus, let’s now make a C++ program that uses dbus-cxx to do the same thing that we just did in Java.

Part V: A Basic C++ Program(Receiving)

Now that we have made some methods to be called in Java, let’s make some methods to be called in C++. We’ll name the methods the same so that it’s easy to see the similarities between the two languages. To start out, let’s make our method. As in Java, we will take in a string, echo it, and then return 0. The implementation is essentially the same as Java, but let’s go over it anyway. Here’s the implementation, using printf as opposed to cout(because I like printf better).

int echoMessage(std::string str) { 
        printf("%s\n", str.c_str() );
        return 0;
}

Simple, right? It’s essentially the same as the Java version. Now that we have this method, let’s go connect to the D-Bus, and export this method out. While the code is slightly more involved, it’s mostly boilerplate so there’s not much of an issue.

        std::shared_ptr<DBus::Dispatcher> dispatcher = DBus::StandaloneDispatcher::create();
        std::shared_ptr<DBus::Connection> connection = dispatcher->create_connection( DBus::BusType::SESSION );

        DBus::RequestNameResponse ret = connection->request_name( "com.rm5248", DBUSCXX_NAME_FLAG_REPLACE_EXISTING );
        if (DBus::RequestNameResponse::PrimaryOwner != ret) return 1;

        std::shared_ptr<DBus::Object> object = connection->create_object("/");

        object->create_method<int(std::string)>("com.rm5248.ReceiveInterface", "echoMessage", sigc::ptr_fun(echoMessage) );

Because this is a little more involved though, I’ll go over this more. First, we need to make a dispatcher. The dispatcher is what actually sends us messages. It is very important that your dispatcher does not go out of scope, or it will be deconstructed and you will not receive any more messages from the D-Bus. Next, we get a connection to the session bus. After we have our connection to the bus, we can request a name. In this case, we are requesting the same name as our Java program, com.rm5248. We then must create a path to export methods on. This is exactly the same as what we did before in the Java program. Here’s a graphic that shows exactly how the two correspond:

Now that we have our connection, we have to connect our method to the connection so that we can call it. To do this, dbus-cxx uses templates and sigc++ to call the correct methods. The template is the most important part of this process. Basically, the first argument of the template is the return value of the method, and the remaining arguments are parameters to the method(7 total). So in this case, the template argument to our method, which returns an int and takes in a string, is <int, string>. Here’s the relevant code:

object->create_method<int(std::string)>("com.rm5248.ReceiveInterface", "echoMessage", sigc::ptr_fun(echoMessage) );

Notice how we don’t pass the method address to the create_method method. We use sigc++ to get a function pointer which is used by dbus-cxx to call it. If we want to call a member function of an object, use sigc::mem_fun(object, object_method). The parameters to create_method are fairly straightforward, the first string is the interface name, the second string is the name of the method as it will show up on the D-Bus, and the pointer is the method that will be called. Before we can check to make sure that this works on the D-Bus, there is one last thing that we must do. We must make an infinite loop at the end of our main so that we don’t exit. While Java DBus will automatically keep the program waiting, dbus-cxx does not. I do this by simply using the std::thread::sleep_for function inside of the loop. Because methods are called in their own threads(in both Java-dbus and dbus-cxx when using the StandaloneDispatcher), there is no issue of deadlock.

If we look at the output from qdbus, we will notice that is (almost) exactly the same as the Java version.

user@machine:~$ qdbus com.rm5248 /
method QString org.freedesktop.DBus.Introspectable.Introspect()
method QString org.freedesktop.DBus.Peer.GetMachineId()
method void org.freedesktop.DBus.Peer.Ping()
signal void org.freedesktop.DBus.Properties.PropertiesChanged(QString interface_name, QVariantMap changed_properties, QStringList invalidated_properties)
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 int com.rm5248.ReceiveInterface.echoMessage(QString)

Here’s the Java version for reference:

user@machine:~$ qdbus com.rm5248 /
method int com.rm5248.ReceiveInterface.echoAndAdd(QString, int, int)
method int com.rm5248.ReceiveInterface.echoMessage(QString)
method QString org.freedesktop.DBus.Introspectable.Introspect()
method void org.freedesktop.DBus.Peer.Ping()

There are fewer methods in the Java version, however we still have the same echoMessage method. If we then call this method, we see output in our C++ program, as well as a return value of 0, exactly as we have seen in the Java program. The output looks exactly the same as from the Java version:

Java version:

user@machine:~$ qdbus com.rm5248 / com.rm5248.ReceiveInterface.echoMessage hello
0

C++ version:

user@machine:~$ qdbus com.rm5248 / com.rm5248.ReceiveInterface.echoMessage hello
0

Now that we have the echoMessage method working, let’s go and add in the second method. So to do that, we have to do two things:

  • define the method
  • put that method onto the D-Bus

The method definition is going to look nearly the same as the Java version, so here’s the important code to export the method:

object->create_method<int(std::string, int, int)>("com.rm5248.ReceiveInterface", "echoAndAdd", sigc::ptr_fun(echoAndAdd) );

Now let’s call the method using qdbus. For reference, here’s what the Java output was:

user@machine:~$ qdbus com.rm5248 / com.rm5248.ReceiveInterface.echoAndAdd Adding 5 9
14

And here’s the result of the C++ call:

user@machine:~$ qdbus com.rm5248 / com.rm5248.ReceiveInterface.echoAndAdd Adding 5 9
14

Alright, we’re able to receive messages using both C++ and Java. Now let’s call methods from one to the other.

Part VI: Calling C++ methods from Java

In order to call methods from Java to C++, Java must know the interface for those methods. Fortunately, we already have an interface made up: ReceiveInterface. In order to call these methods, we must get a remote object to whatever is providing these methods. To do that is very simple. All we have to do is to use our DBusConnection and get a remote object. In fact, here is all of the code required to get a remote object(try/catch omitted for brevity):

DBusConnection conn = DBusConnection.getConnection(DBusConnection.DBusBusType.SESSION);
ReceiveInterface recvInterface = conn.getRemoteObject("com.rm5248",
                    "/",
                    ReceiveInterface.class)

Like the receiving portion, we first get a DBusConnection. Once we have the connection, we get a remote object from the connection. To do that, we specify the unique D-Bus address, the path, and the interface. In this case, this interface will resolve to com.rm5248.ReceiveInterface, which is exactly what qdbus shows as output. Note that when doing Java to Java communication the third parameter may not be needed, but it is always a good idea to add it just in case.

Now that we have our remote object, we can call methods on it just as if it were a local variable in our program:

int returnValue = recvInterface.echoMessage("Hello C++ from Java!");
System.out.println(returnValue);

Now, run the C++ program that we made in Part VI(receive), and then run your Java program. If everything is good, your C++ program should print out “Hello C++ from Java!” and your Java program should print out “0”.

Awesome! We can now communicate from Java to C++. Let’s now go the other direction.

Part VII: Calling Java methods from C++

Again, the C++ is slightly more involved, but about half of it is boilerplate code. So, let’s start out with the first half, which makes the D-Bus connection and gets a remote object.

std::shared_ptr<DBus::Dispatcher> dispatcher = DBus::StandaloneDispatcher::create();
std::shared_ptr<DBus::Connection> connection = dispatcher->create_connection( DBus::BusType::SESSION );

std::shared_ptr<DBus::ObjectProxy> object = connection->create_object_proxy("com.rm5248", "/");

When we get the remote object, we must specify both the unique D-Bus address and the path of the object. Now that we have the object, let’s go and put some methods onto it. The way we make methods on it is very similar to how we created methods on ourselves earlier. Both use templates to figure out what method to call. So, here’s our first method definition:

DBus::MethodProxy<int(std::string)>& echoMessage = *(object->create_method<int(std::string)>("com.rm5248.ReceiveInterface", "echoMessage"));

We use the templates to define the signature of the method. In this case, the signature is a method that returns an int and takes a string. The two parameters to the create_method are the interface of the method, and the name of the method. Now that we have that method defined, let’s go ahead and define the other method:

DBus::MethodProxy<int(std::string, int, int)>& echoAndAdd = *(object->create_method<int(std::string, int, int)>
                ("com.rm5248.ReceiveInterface", "echoAndAdd"));

Now that we have these two method proxies, we can call the methods as though they were local methods. Here’s the code to do that, and print the results out:

int firstResult = echoMessage("Hello Java from C++!");
printf("%d\n", firstResult);
        
int secondResult = echoAndAdd("Testing the addition", 42, 19);
printf("%d\n", secondResult);

To test this, let’s fire up JavaReceive, which we made in Part III. Once that has started up, run the program that we just made(send). This is the output that you should get in Java:

Hello Java from C++!
Testing the addition

And here is the output that you should get in C++:

0
61

Excellent! We can now communicate Java->C++, and C++->Java. Note that Java->Java and C++->C++ are not explicitly stated; This is because we have already built these parts! If you run receive and then send, notice how you get the same output as we just got in this part. As well, if you run JavaReceive and then JavaSend, the two programs communicate perfectly.

Part VIII: Type differences between dbus-java and dbus-cxx

Since we have the basic communication ideas down between both Java and C++, it’s now time to talk about the differences. Let’s go take a look at the Notification XML that we looked at back in Part III. Here’s the relevant part that we’re going to talk about:


<node>
  <!-- ... -->
  <interface name="org.freedesktop.Notifications">
    <method name="Notify">
      <arg type="s" name="arg_0" direction="in">
      </arg>
      <arg type="u" name="arg_1" direction="in">
      </arg>
      <arg type="s" name="arg_2" direction="in">
      </arg>
      <arg type="s" name="arg_3" direction="in">
      </arg>
      <arg type="s" name="arg_4" direction="in">
      </arg>
      <arg type="as" name="arg_5" direction="in">
      </arg>
      <arg type="a{sv}" name="arg_6" direction="in">
      </arg>
      <arg type="i" name="arg_7" direction="in">
      </arg>
      <arg type="u" name="arg_8" direction="out">
      </arg>
    </method>
  <!-- ... -->
  </interface>
</node>

How would we call this from Java? Well, that’s a bit of a tricky question. Without cheating and using the provided tool to make a Java interface, there are a few things that we need to know. Those are:

  • What is the interface?
  • What are the types of the parameters?

This information is given to us in the XML. In fact, it’s clearly stated. The interface is org.freedekstop.Notifications, and the type of the parameters is given in terms of D-Bus types. So, here’s a comparison between dbus-java, dbus-cxx, and the D-Bus introspection XML.

Javadbus-cxxD-Bus type
Stringstd::strings
Liststd::vectora
Map<A,B>std::map<A,B>a{s,v}
Byteuint8_ty
intint32_ti
UInt32uint32_tu
VariantVariantv

Using this information, we can now make a Java interface.

package org.freedesktop;

import java.util.List;
import java.util.Map;
import org.freedesktop.dbus.interfaces.DBusInterface;
import org.freedesktop.dbus.types.UInt32;
import org.freedesktop.dbus.types.Variant;


public interface Notifications extends DBusInterface{
	public UInt32 Notify(String app_name, UInt32 id, String icon, String summary, String body, List<String> actions, Map<String,Variant> hints, int timeout);
}

You’ll see how we have created the right number of parameters with the correct type in the method signature. Our interface is also in the package org.freedesktop. When we use this Notifications interface, we have to use it as such:

DBusConnection conn = DBusConnection.getConnection(DBusConnection.DBusBusType.SESSION);
Notifications notify = conn.getRemoteObject("org.freedesktop.Notifications", 
                    "/org/freedesktop/Notifications",
                    Notifications.class);

This is equivalent to:

user@machine:~$ qdbus org.freedesktop.Notifications /org/freedesktop/Notifications org.freedesktop.Notifications.Notify

Now that we have our interface, let’s get a remote object and call the Notify method on it. We’re not going to go over what all of the parameters to this method do, because this is not a tutorial on how to use the notification-daemon. Anyway, here’s the code which calls the method, after we have gotten the remote object:

Map<String, Variant> hints = new HashMap<String, Variant>();
hints.put("urgency", new Variant<Byte>((byte) 2));
notify.Notify("", new UInt32(0), "", "This is a test", "Again, this is only a test", new LinkedList<String>(), hints, -1);

Once we run this program, we now get a notification in one corner of our screen. Depending on how your computer is set up, this will probably look different. This is what it looks like on my computer:

Now if we wanted to, we could implement the other methods on this interface. We’re not going to do that. However, we will talk briefly about the dbus-cxx version of this. This is what the dbus-cxx signature of the method looks like:

DBus::MethodProxy<uint32_t(std::string,uint32_t,std::string,std::string,std::string,std::vector<std::string>,std::map<std::string,DBus::Variant>,int32_t)> proxy;

Hopefully you can see how you use templates to make a method now.

To make sure that you understand exactly what the naming conventions and how you define what methods to call from using Java, C++ and qdbus, here’s a graphic that spells it out:

Part IX: Receiving Signals(C++)

First, what is a signal? Basically, a signal is a method call that doesn’t return. A signal is also sent out to all programs which have stated that they would like to receive that signal. If you do not ask to receive a signal, you will not get it. It’s similar to a publish/subscribe paradigm. Now, while the rest of this tutorial has been showing you how to do something in Java first, it’s actually much easier to receive and send signals in C++. The way we do it is actually very similar to how we defined methods to be called before. We ask to receive a signal and give the method we want called when that signal is sent out. First, let’s quickly define our signal handling method. This signal handling method will take in an int, a string, and another int. Note that signal handlers cannot return anything, so the return type is void. Here’s our signal handler. All the signal handler does is make sure the two ints are valid indecies into the string, and then print out the characters at those positions. Once it does that, it prints out the entire string.

void getSignal(int a, std::string str, int b){
        char* cstr = (char*)str.c_str();
        
        if( a < strlen(cstr) ){
                printf( "%c\n", cstr[a]);
        }
 
        if( b < strlen(cstr) ){
                printf( "%c\n", cstr[b] );
        }
        
        printf("Entire string passed in: %s\n", cstr);
}

As you can see, there’s no real difference between defining a method to receive a signal and defining a method to be called. Now, let’s connect that signal up.

DBus::SignalMatchRule rule =
                DBus::MatchRuleBuilder::create()
                .set_path( "/" )
                .set_interface( "com.rm5248.SignalInterface" )
                .set_member( "Generic_Signal" )
                .as_signal_match();
        std::shared_ptr<DBus::SignalProxy<void(int,std::string,int)>> signal = connection->
                create_free_signal_proxy<void(int,std::string,int)>(rule);

Again, this is very similar to how we defined a method to be called, including the template arguments. However, we use a DBus::SignalProxy to catch the signal instead of a DBus::Object. A note about this code: you need to make a new DBus::SignalProxy for each signal that you want to catch. When we made a DBus::Object, you only had to make one object in order to export multiple methods.

Now that we have our signal proxy set up, let’s compile and run this. We will now use dbus-send to send a signal, because qdbus does not have support for sending signals. The syntax is similar to qdbus though, so it shouldn’t look too odd:

user@machine:~$ dbus-send / com.rm5248.SignalInterface.GenericSignal int32:3 string:"This is a very long message with many characters in it" int32:8

Note that when using dbus-send, you must explicitly state what the types of the parameters that you are sending out are. Also note how we are sending out the signal. We are sending it to a specific destination, If you have dbus-monitor open, you should now see this message:

signal time=1612578736.911895 sender=:1.664 -> destination=(null destination) serial=2 path=/; interface=com.rm5248.SignalInterface; member=GenericSignal
   int32 3
   string "This is a very long message with many characters in it"
   int32 8

As you can see, ‘signal’ is the first word here, as opposed to earlier when it was ‘method_call’ or ‘method_return’. Now, if we look at the output for recv_sig, notice that we now have some new output:

user@machine:$ ./dbus_c++_receive_signal/recv_sig
s
a
Entire string passed in: This is a very long message with many characters in it

Excellent! We have now received our first signal. Now let’s go and send out signals so we can receive them.

Part X: Sending Signals(C++)

Sending out signals is amazingly easy with dbus-cxx. Once you init your connection, just make a DBus::signal with the correct template methods, and the path you want to get the interface for.

std::shared_ptr<DBus::Signal<void(int,std::string,int)>> signal_send = connection->
        create_free_signal<void(int,std::string,int)>("/", "com.rm5248.SignalInterface", "GenericSignal");

signal_send->emit( 5, "Sending out signals is fun!", 17);

How to emit the signal is also very similar to how you call a method. Simply call the method as though it were a local method, and the signal will be sent out.

Part XI: Receiving Signals(Java)

Receiving signals in Java is more complicated than in C++. There are two things that we must do. First, we must make an interface that extends DBusInterface, as though we were implementing methods to be called by the D-Bus. If we want to export methods, we can define them in here, but for the purposes of this section we wont. Now, inside of this method we must make a class which extends DBusSignal. Since we are going to make the same signal that we made in C++, we need to make sure that a few things are the same. First, the interface must be defined, as com.rm5248.SignalInterface. Next, our DBusSignal must be defined in this class, as a GenericSignal. There are a few things to know about how to define the signal. First, it must be public. The constructor must also be public. When you are creating the signal, any parameters that you give it to construct the signal with must be given to the super constructor. If you do not do this, you will be unable to receive or send the proper signals. Here’s the proper implementation of a signal:

package com.rm5248;


import org.freedesktop.dbus.exceptions.DBusException;
import org.freedesktop.dbus.interfaces.DBusInterface;
import org.freedesktop.dbus.messages.DBusSignal;

public interface SignalInterface extends DBusInterface {

    public class GenericSignal extends DBusSignal {

        public final int a;
        public final String str;
        public final int b;

        public GenericSignal(String path, int a, String str, int b) throws DBusException {
            super(path, a, str, b);
            this.a = a;
            this.str = str;
            this.b = b;
        }

    }
}

Now that we have our signal, we can connect to the D-Bus and request our bus name. We then install our signal handler, in this case implemented as an anonymous inner class:

conn.addSigHandler(SignalInterface.GenericSignal.class, new DBusSigHandler<SignalInterface.GenericSignal>() {

                @Override
                public void handle(SignalInterface.GenericSignal sig) {

                }

            });

The two parameters are as follows: The first one is the interface of the signal that we are looking for. On the D-Bus, SignalInterface.GenericSignal.class will resolve to com.rm5248.SignalInterface, with a member of GenericSignal. The next parameter is the signal handler. Signal handlers must implement the handle() function. The parameter to this function is determined by the generic type of the class.

Now if we run this program and send signals out from either our C++ program or using dbus-send, notice how we now get a signal in both programs at the same time. If we run both the Java receive and the C++ receive as provided, we will get an error because they are both trying to use the address com.rm5248. Since these addresses must be unique, simply change one of the programs to request a different bus.

Part XII: Sending Signals(Java)

Now that we have defined our interface, we can now send out a signal. Sending out a signal is also very easy. All we have to do is create a new signal, and then tell the connection that we want to send it out. This is the entire program needed to send out a signal(try/catch omitted for brevity):

DBusConnection conn = DBusConnection.getConnection(DBusConnection.DBusBusType.SESSION);
SignalInterface.GenericSignal sig = new SignalInterface.GenericSignal("/", 6, "Hello there my friends, this is a test of the emergency broadcast system", 22);
conn.sendMessage(sig);

Again, if we run both the Java signal receiver and the C++ signal receiver(with different D-Bus addresses), we will get the same signal sent to both of us.

Conclusion

D-Bus is a very powerful IPC mechanism. There are a number of options which it gives you that you can use to connect different processes together.

22 thoughts on “D-Bus Tutorial

  1. Thank you very much for this tutorial, you saved me a lot of time getting into D-Bus. The provided example files are easy to understand and great to dive into the D-Bus ocean.

    Some people might have problems to install dbus-cxx (so did I on ubuntu). I tried to get it from SVN as recommended and got the latest stable version. While installing, the strangest errors popped up and after solving one, the next appeared.

    Finally I found a simple step by step instruction from Andrew Montag that helped me a lot and i recommend it to everyone who tries to install the latest dbus-cxx:
    http://sourceforge.net/mailarchive/message.php?msg_id=30077717

    – Andreas

  2. Thank you very much for this perfect tutorial! It is very easy to understand and useful for me.
    I have been looking for such documentation for a long time.

  3. Very nice and detailed sample usages that clearly and simply explain how D-Bus works.

    Based on your samples I also found the corresponding commands using dbus-send which comes with D-Bus.

    For a method call:
    dbus-send –print-reply –dest=’com.rm5248′ / com.rm5248.ReceiveInterface.echoMessage string:’Hello’

    For a signal:
    dbus-send –session –type=signal / com.rm5248.SignalInterface.GenericSignal int32:6 string:’Hello There, this is my first test’ int32:22

    Nice job

  4. Cool tutorial. I had a problem with running my app:
    org.freedesktop.dbus.exceptions.NotConnected: Disconnected
    The reason was the hashmap mustn’t be empty.
    So I’ve added an “urgency” item and it’s working.
    Thank you for your work. Don’t know when this tutorial was written, but now in 2014 it’s still valuable – even though I’m writing in Scala.

  5. excellent tutorial, those examples just work….the only thing I had to do is linking libraries from /usr/share/java and to choose a correct java version on Ubuntu 12.04 x64…Just wondering what is needed to make it the recieve example a productive piece of code…!?

  6. Here an update to run it on Debian (LMDE) with Eclipse Luna
    – Instead of downloading dbus-java-2.7.tar.gz from http://freedesktop.org/wiki/Software/DBusBindings/, I installed dbus-java-bin via Synaptic.
    – Then I imported your examples as existing projects into my eclipse workspace.
    – I had to change the libraries:
    in Eclipse, right-click the project, Properties, Java Build Path, Libraries tab, remove ‘libdbus-java-2.7.jar’ and ‘JRE System Library [JavaSE-1.6]’ and Add External JARs… ‘/usr/share/java/dbus.jar’, and Add Library… ‘JRE System Library’ (workspace default).
    – Then it would run
    Thanks!

  7. Hi, i cant solve this problem:
    Exception in thread “main” java.lang.UnsatisfiedLinkError: no unix-java in java.library.path
    at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1865)
    at java.lang.Runtime.loadLibrary0(Runtime.java:870)
    at java.lang.System.loadLibrary(System.java:1119)
    at cx.ath.matthew.unix.UnixSocket.(UnixSocket.java:33)
    at org.freedesktop.dbus.Transport.connect(Unknown Source)
    at org.freedesktop.dbus.Transport.(Unknown Source)
    at org.freedesktop.dbus.DBusConnection.(Unknown Source)
    at org.freedesktop.dbus.DBusConnection.getConnection(Unknown Source)

    any idea?

    Thank you

  8. Firstly, thanks for your share.
    Could you give me more messages about “For Java, make sure that you have the jar and the required files. ” What is the jar and the required files? Because I met some error when I run the examples.

    Thanks again!

    • The required files are the libmatthew jar files that are needed to run the examples. These jar files must be on the classpath.

      If I remember correctly, those jar files are:
      dbus.jar
      unix.jar(from libmatthew)
      hexdump.jar(from libmatthew)

      If you’re on Debian, you should be able to install these using:
      apt-get install dbus-java

      The jar files will be in /usr/share/java

  9. I was unable to get SVN revision 213 to compile. Rolling back to rev. 207 removed the offending code, but it might be worth taking look to make sure what you committed was what you intended.

    arg.cpp: In member function ‘std::string Arg::stubsignature()’:
    arg.cpp:84:34: error: no matching function for call to ‘signature(DBus::Type)’
    return DBus::signature( type() );
    ^
    82: std::string Arg::stubsignature()
    83: {
    84: return DBus::signature( type() );
    85: }
    86:
    87: DBus::Type Arg::type()
    88: {
    89: if ( not signature.is_singleton() ) return DBus::TYPE_INVALID;
    90: //if ( not signature.begin().is_basic() ) return DBus::TYPE_INVALID;
    91: return signature.begin().type();
    92: }

      • I’m using gcc 4.8.2 on Ubuntu 14.04 (arm-linux.gnueabihf toolchain). I’ve also tried compiling on my development machine, which has gcc 4.8.4 on Ubuntu 14.04 and Intel i386-linux-gnu toolchain. Both have the same complaint.
        Even after building rev. 207, I still can’t get either dbus-cxx-introspect or dbus-cxx-xml2cpp to do anything useful. The former hangs and the latter produces no output.
        Thanks!

        • I will take a look at it at some point, but it may be a few weeks before I have the time.

          If you are willing and able to look at the code and figure out what is wrong, send a patch to the dbus-cxx mailing list and I can apply it.

          (It compiles cleanly for me on Debian 8, so I don’t have any ideas why it wouldn’t work on 14.04)

          • I can probably afford to spend a small amount of time on it. If you can tell me the file name and line number of the function that matches the signature “signature(DBus::Type)”, I’ll start from there.
            Thanks!

          • I just took a quick look at it, that error is only in the tools. Earlier versions should work.

            Otherwise, try this patch, and we can move the discussion there or to the mailing list, since this is getting too deep for wordpress to handle.

        • A bit late, but I came across this problem myself, and others might. If you change the variable c in introspect.c from a char to an int and recompile, dbus-cxx-introspect will not hang. Like me, maybe you’re using a 64-bit compiler that stores ints and chars differently but doesn’t through an implicit cast error.

  10. Sorry to keep bugging you here. Perhaps you didn’t see my comments on github. And I’ve been waiting since September for access to the mailing list, so this is my only choice. Is there any documentation on using dbus-cxx to send user-defined data (i.e. a struct).
    Thanks,

Leave a Reply

Your email address will not be published. Required fields are marked *