D-Bus is a message bus system, a simple way for applications to talk to one another The low-level API for D-Bus is written in C but most of the documentation and code is written for a higher level binding, such as Python or GLib. Here I provide tutorial/howto for a basic server and client using the C API directly, including example code. The D-Bus website has Doxygen documentation for the C API.
WARNING: you should NOT use this API unless you absolutely have to. This is NOT designed for application developers. Application developers should use one of the bindings if at all possible. If you are writing in C then the GLib bindings are recommended.
It's also worth mentioning here that this is definitely incomplete in several respects. Firstly, you generally want to intograte with some main loop. The low-level API provides some support for callbacks and so on to help you do this. Secondly, while this will allow you to send a receive messages, that in itself does not make for a conformant DBus implementation. There are several things which are required (such as introspection, the object tree, error messages and so on) and which you need to manage by hand when using the low-level API. This is why the high-level bindings and implementations are recommended; they do all these things for you.
A lot of the code is common no matter what you want to do on the Bus. First you need to connect to the bus. There is normally a system and a session bus. The D-Bus config may restrict who can connect to the system bus.
DBusError err; DBusConnection* conn; int ret; // initialise the errors dbus_error_init(&err);
// connect to the bus
conn = dbus_bus_get(DBUS_BUS_SESSION, &err);
if (dbus_error_is_set(&err)) {
fprintf(stderr, "Connection Error (%s)\n", err.message);
dbus_error_free(&err);
}
if (NULL == conn) {
exit(1);
}
The simplest operation is sending a broadcast signal to the bus. To do this you need to create a DBusMessage object representing the signal, and specifying what object and interface the signal represents. You then need to add any parameters to it as appropriate, and send it to the bus. Finally you need to free the message. Several methods return false on out of memory, this should be checked for and handled.
dbus_uint32_t serial = 0; // unique number to associate replies with requests
DBusMessage* msg;
DBusMessageIter args;
// create a signal and check for errors
msg = dbus_message_new_signal("/test/signal/Object", // object name of the signal
"test.signal.Type", // interface name of the signal
"Test"); // name of the signal
if (NULL == msg)
{
fprintf(stderr, "Message Null\n");
exit(1);
}
// append arguments onto signal
dbus_message_iter_init_append(msg, &args);
if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &sigvalue)) {
fprintf(stderr, "Out Of Memory!\n");
exit(1);
}
// send the message and flush the connection
if (!dbus_connection_send(conn, msg, &serial)) {
fprintf(stderr, "Out Of Memory!\n");
exit(1);
}
dbus_connection_flush(conn);
// free the message
dbus_message_unref(msg);
Calling a remote method is very similar to sending a signal. You need to create the message and specify the name on the bus it is being send to, and what object, interface and method is being called. You then add parameters to the message as above. If you need a reply to the message you need to use a different method to send the message. This gives you a pending reply object which you can block on to wait for the reply and get another DBusMessage* as the reply. From this you can read the parameters returned by the remote method.
DBusMessage* msg;
DBusMessageIter args;
DBusPendingCall* pending;
msg = dbus_message_new_method_call("test.method.server", // target for the method call
"/test/method/Object", // object to call on
"test.method.Type", // interface to call on
"Method"); // method name
if (NULL == msg) {
fprintf(stderr, "Message Null\n");
exit(1);
}
// append arguments
dbus_message_iter_init_append(msg, &args);
if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, ¶m)) {
fprintf(stderr, "Out Of Memory!\n");
exit(1);
}
// send message and get a handle for a reply
if (!dbus_connection_send_with_reply (conn, msg, &pending, -1)) {
// -1 is default timeout
fprintf(stderr, "Out Of Memory!\n");
exit(1);
}
if (NULL == pending) {
fprintf(stderr, "Pending Call Null\n");
exit(1);
}
dbus_connection_flush(conn);
// free message
dbus_message_unref(msg);
bool stat;
dbus_uint32_t level;
// block until we receive a reply
dbus_pending_call_block(pending);
// get the reply message
msg = dbus_pending_call_steal_reply(pending);
if (NULL == msg) {
fprintf(stderr, "Reply Null\n");
exit(1);
}
// free the pending message handle
dbus_pending_call_unref(pending);
// read the parameters
if (!dbus_message_iter_init(msg, &args))
fprintf(stderr, "Message has no arguments!\n");
else if (DBUS_TYPE_BOOLEAN != dbus_message_iter_get_arg_type(&args))
fprintf(stderr, "Argument is not boolean!\n");
else
dbus_message_iter_get_basic(&args, &stat);
if (!dbus_message_iter_next(&args))
fprintf(stderr, "Message has too few arguments!\n");
else if (DBUS_TYPE_UINT32 != dbus_message_iter_get_arg_type(&args))
fprintf(stderr, "Argument is not int!\n");
else
dbus_message_iter_get_basic(&args, &level);
printf("Got Reply: %d, %d\n", stat, level);
// free reply and close connection
dbus_message_unref(msg);
These next two operations require reading messages from the bus and handling them. There is not a simple way in the C API as of 0.50 to do this. Because D-Bus is designed to use OO bindings and an event based model all the methods that handle the messages on the wire use callbacks. I have submitted a patch which should make it into the next release which allows messages to be read and marshalled from the wire in a non-blocking operation. This is the dbus_connection_read_write() method, which is required to use the code on this page.
To receive a signal you have to tell the bus what signals you are interested in so they are sent to your application, then you read messages off the bus and can check what type they are and what interfaces and signal names each signal represents.
// add a rule for which messages we want to see
dbus_bus_add_match(conn,
"type='signal',interface='test.signal.Type'",
&err); // see signals from the given interface
dbus_connection_flush(conn);
if (dbus_error_is_set(&err)) {
fprintf(stderr, "Match Error (%s)\n", err.message);
exit(1);
}
// loop listening for signals being emmitted
while (true) {
// non blocking read of the next available message
dbus_connection_read_write(conn, 0);
msg = dbus_connection_pop_message(conn);
// loop again if we haven't read a message
if (NULL == msg) {
sleep(1);
continue;
}
// check if the message is a signal from the correct interface
// and with the correct name
if (dbus_message_is_signal(msg, "test.signal.Type", "Test")) {
// read the parameters
if (!dbus_message_iter_init(msg, &args))
fprintf(stderr, "Message has no arguments!\n");
else if (DBUS_TYPE_STRING != dbus_message_iter_get_arg_type(&args))
fprintf(stderr, "Argument is not string!\n");
else {
dbus_message_iter_get_basic(&args, &sigvalue);
printf("Got Signal with value %s\n", sigvalue);
}
}
// free the message
dbus_message_unref(msg);
}
Note that the use of sleep in this example is to simulate some other thing we are blocking on, such as select. If the only thing happening in this thread is the D-Bus communication then the dbus_connection_read_write call can take a timeout parameter, which is how many ms to block for. -1 blocks forever.
To expose a method which may be called by other D-Bus applications you have to listen for messages as above, then when you get a method call corresponding to the method you exposed you parse out the parameters, construct a reply message from the original and populate its parameters with the return value. Finally you have to send and free the reply.
// loop, testing for new messages
while (true) {
// non blocking read of the next available message
dbus_connection_read_write(conn, 0);
msg = dbus_connection_pop_message(conn);
// loop again if we haven't got a message
if (NULL == msg) {
sleep(1);
continue;
}
// check this is a method call for the right interface and method
if (dbus_message_is_method_call(msg, "test.method.Type", "Method"))
reply_to_method_call(msg, conn);
// free the message
dbus_message_unref(msg);
}
void reply_to_method_call(DBusMessage* msg, DBusConnection* conn)
{
DBusMessage* reply;
DBusMessageIter args;
DBusConnection* conn;
bool stat = true;
dbus_uint32_t level = 21614;
dbus_uint32_t serial = 0;
char* param = "";
// read the arguments
if (!dbus_message_iter_init(msg, &args))
fprintf(stderr, "Message has no arguments!\n");
else if (DBUS_TYPE_STRING != dbus_message_iter_get_arg_type(&args))
fprintf(stderr, "Argument is not string!\n");
else
dbus_message_iter_get_basic(&args, ¶m);
printf("Method called with %s\n", param);
// create a reply from the message
reply = dbus_message_new_method_return(msg);
// add the arguments to the reply
dbus_message_iter_init_append(reply, &args);
if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_BOOLEAN, &stat)) {
fprintf(stderr, "Out Of Memory!\n");
exit(1);
}
if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_UINT32, &level)) {
fprintf(stderr, "Out Of Memory!\n");
exit(1);
}
// send the reply && flush the connection
if (!dbus_connection_send(conn, reply, &serial)) {
fprintf(stderr, "Out Of Memory!\n");
exit(1);
}
dbus_connection_flush(conn);
// free the reply
dbus_message_unref(reply);
}
Each connection to the bus is assigned a unique name, which can be used to call methods on that connection. However, this name is transient. If you want other applications to have a known name to connect to you can request on on the bus. For simplicity I don't cope with the case where someone else already owns the name.
// request a name on the bus
ret = dbus_bus_request_name(conn, "test.method.server",
DBUS_NAME_FLAG_REPLACE_EXISTING
, &err);
if (dbus_error_is_set(&err)) {
fprintf(stderr, "Name Error (%s)\n", err.message);
dbus_error_free(&err);
}
if (DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER != ret) {
exit(1);
}
That should be all you need to write a simple server and client for D-Bus using the C API. The code snippets above come from dbus-example.c which you can download and test. It contains code for all four operations above.
Copyright © 2005 Matthew Johnson <dbus@matthew.ath.cx> This document is licensed under the GNU GPL Version 2.