Best D-Bus Practices
by havoc
Using various D-Bus APIs I’ve noticed all kinds of interesting
ideas for how to design them. You should avoid many of these ideas.
First, don’t overthink the basic D-Bus concepts.
- An object path is used to refer to an
object. Object means object. Like GObject,
java.lang.Object, or your choice of object. But it does not mean
anything magic. If you have an object, then it should have an
object path. - An interface is used to name an interface. For
example, if in Java you would say “interface Introspectable”, then
in D-Bus you would also say “interface Introspectable.” -
A bus name admittedly requires learning something new, but
nothing too outrageous. It is a name assigned to a thing on the bus.
The bus only has one thing on it: connections. A connection is a
DBusConnection, usually each app has one connection to the bus.
So a bus name is used to refer to an app, pretty much. Apps are
programs. These programs contain objects.
Pulling this together, to talk to an object you need to know an
interface it has, you need an object path (equivalent to a pointer in
C or Java object reference), and you need a bus name (which you could
think of as the host name, the point is it gets your message to the
app that you want to talk to).
Here is what you should not do. Gaim (now Pidgin) has one object,
called GaimObject, one bus name, called GaimService, and one
interface, GaimInterface. The interface, which is implemented by the
object, has 499 methods on it. The methods are named things
like GaimBlistNodeIsContact(), and take an integer ID for an object as
the first arg. Let’s write this in Java:
interface GaimInterface { int GaimBlistNodeIsBuddy(int blistNodeId); int GaimContactNew(); String GaimContactGetAlias(int contactId); int GaimContactOnAccount(int contactId, int accountId); // ... add 494 more methods }
I’m just picking on Gaim because I was just coding to it. Other people
make the same mistake. You don’t need to invent a new kind of integer
ID! That is what the object path is, it’s an id for an object. Gaim
has the whole hash table from IDs to objects, but DBusConnection
already has a hash table for that (see dbus_connection_register_object_path()).
Instead of net.sf.gaim.GaimInterface.GaimBlistNodeIsBuddy(int blistNodeId), the
correct method would be net.sf.gaim.BlistNode.IsBuddy().
Anyway. Second thing to do in your D-Bus API is follow the
naming conventions. Those are documented here.
Third recommendation is to avoid a billion little round trips. (Michael used to
endlessly try to get people to stop this with CORBA, too. He was
right.) When doing any IPC thing, your performance killer is not
size of data, but blocking for one thing before you do the next thing.
In other words you don’t want to keep having to wait for the other
end, you want to be able to dump a bunch of stuff into the socket, and
then read a bunch of stuff off the socket. You don’t want to
block at all, when you can help it, but blocking over and over is
worse. Blocking over and over happens when one method call requires
information from a first method call.
To pick on Gaim again since I was just using its D-Bus API, its API
requires you to get the accounts, then with a separate method call get
each property of each account, then with a separate method call get
each property of each buddy. Instead, in an IPC API you should be able
to do something like get all the buddies and all their properties in a
single method call, then keep your info up-to-date by receiving change
signals. Or for example, instead of GaimBuddyIsOnline(), have
GaimBuddyGetProperties(). D-Bus even has a standard interface for
object properties (the dbus-glib bindings will export GObject props
for you, too), and I recently proposed a “GetAll” method to be added to
that interface.
This is especially important for application startup. In designing
D-Bus APIs that apps will call on startup, you should strive to be
sure the app can just fire off messages and then get replies only
later once the main loop is running.
A useful hint here: match your signals by well-known bus name
(“hostname”) rather than by unique bus name (“IP address”). This
avoids the need for “name resolution” and also avoids the need to
re-add the match rule when the bus name gets a new owner.
Fourth, use the bindings. If you don’t use a D-Bus binding you
probably will not get the introspection stuff right, which messes up
interoperability with certain bindings, and also keeps your app from
showing in dbus-viewer type of tools. Bindings can also automatically
implement object properties for you and encourage you to properly use
object paths and other D-Bus concepts.
I do think the C binding situation needs work; I don’t think we have
dbus-glib 100% sorted out yet, and probably won’t until GLib itself
has a good introspection system. In the Mugshot client I cooked
up a cheesy helper API, which has its own set of problems, so be
careful copying it. It also relies on dbus-glib for certain hard bits
(main loop integration). But the Python bindings are in very good
shape, and afaik some of the others are as well.
(This post was originally found at http://log.ometer.com/2007-05.html#17)