follow me on Twitter

    Blog has moved to, see May, 2007 on new site

    Blog entries for May, 2007

    This post moved to

    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.