DBus-glib with org.freedesktop.DBus.GLib.Async

/!\ Note: The example code is outdated. It needs to be converted from dbus-glib to GIO's gdbus.

Download the samples

Use Vala to make all this a lot more easy

First of all

If you are a vi user:

export $EDITOR vi

If you are a emacs user:

export $EDITOR emacs

Root build environment

Setting up the build environment. I'm not going to detail this as the subject is addressed at other online places.

$EDITOR configure.ac

AC_PREREQ(2.59)
AC_INIT([program],[0.0.1],[program-list@hello.org])
AC_CONFIG_SRCDIR([src/program.c])
AM_INIT_AUTOMAKE([dist-bzip2])
AC_SUBST(PACKAGE_URL, [http://www.hello.org/program])
CFLAGS="$CFLAGS"
AC_PROG_CC
AC_PROG_INSTALL
AC_PROG_LIBTOOL
DBUS_REQUIRED=0.60

PKG_CHECK_MODULES(PROG, glib-2.0 gthread-2.0 
        dbus-1 >= $DBUS_REQUIRED 
        dbus-glib-1 >= $DBUS_REQUIRED)
AC_SUBST(PROG_CFLAGS)
AC_SUBST(PROG_LIBS)

AC_PATH_PROG(DBUSBINDINGTOOL, dbus-binding-tool)
AC_SUBST(DBUSBINDINGTOOL)

GLIB_GENMARSHAL=`$PKG_CONFIG glib-2.0 --variable=glib_genmarshal`
AC_SUBST(GLIB_GENMARSHAL)

AC_OUTPUT([
Makefile
data/Makefile
src/Makefile
])

touch NEWS README AUTHORS ChangeLog
mkdir data src
echo "SUBDIRS = data src" > Makefile.am

Preparing the m-test-service.xml file for dbus-binding-tool

cd data ; $EDITOR m-test-service.xml

<?xml version="1.0" encoding="UTF-8" ?>
<node name="/org/hello/TestService">
  <interface name="org.hello.TestService">
    <method name="Ping">
      <annotation name="org.freedesktop.DBus.GLib.Async" value="true"/>
      <arg type="s" name="pong_str" direction="out" />
    </method>
  </interface>
</node>

Preparing the .service file

$EDITOR program.service.in

[D-BUS Service]
Name=org.hello.TestService
Exec=@bindir@/program

Build environment for `data/`

$EDITOR Makefile.am

servicedir = $(DBUS_SERVICES_DIR)
service_in_files = program.service.in
service_DATA = program.service
CLEANFILES = $(service_DATA)
EXTRA_DIST = $(service_in_files)
%.service: %.service.in
        @sed -e "s|\@bindir\@|$(bindir)|" $< > $@

The program itself

We're already going to make the marshal.h and .c files because as your remote API grows, you will likely have to add at least a few marshallers. To add marshallers just make lines like "RETURN_TYPE:PARAM1_TYPE, PARAM2_TYPE" in program-marshal.list. For example "VOID:STRING,BOXED" for program_marshal_VOID__STRING_BOXED. If you don't know what marshallers are, check out the documentation about GObject signals for example.

cd ../src
touch program-marshal.list program.c 
touch m-test-service.h m-test-service.c

Build environment

$EDITOR Makefile.am

INCLUDES = $(PROG_CFLAGS)

program-marshal.h: program-marshal.list
        ($(GLIB_GENMARSHAL) --prefix=program_marshal program-marshal.list --header) > xgen-gmh \
        && (cmp -s xgen-gmh program-marshal.h || cp xgen-gmh program-marshal.h) \
        && rm -f xgen-gmh xgen-gmh~

program-marshal.c: program-marshal.list
        ($(GLIB_GENMARSHAL) --prefix=program_marshal program-marshal.list --body) > xgen-gmc \
        && cp xgen-gmc program-marshal.c \
        && rm -f xgen-gmc xgen-gmc~

m-test-service-glue.h: $(top_builddir)/data/m-test-service.xml
        $(DBUSBINDINGTOOL) --mode=glib-server --output=$@ --prefix=m_test_service $^

bin_PROGRAMS = program
dbus_sources = m-test-service-glue.h


BUILT_SOURCES = $(dbus_sources)

program_SOURCES = $(dbus_sources) \
        program.c \
        program-marshal.h \
        program-marshal.c \
        m-test-service.c \
        m-test-service.h
        
program_LDADD = $(PROG_LIBS)

EXTRA_DIST = program-marshal.list

CLEANFILES = m-test-service-glue.h \
        program-marshal.h \
        program-marshal.c

The main of the program

$EDITOR program.c

#include <glib.h>
#include <glib/gthread.h>
#include <dbus/dbus.h>
#include <dbus/dbus-glib-lowlevel.h>
#include <dbus/dbus-glib.h>

#include "m-test-service.h"
#include "m-test-service-glue.h"

static gpointer
dbus_register_object (DBusGConnection *connection,
                      DBusGProxy *proxy,
                      GType object_type,
                      const DBusGObjectInfo *info,
                      const gchar *path)
{
        GObject *object = g_object_new (object_type, NULL);
        dbus_g_object_type_install_info (object_type, info);
        dbus_g_connection_register_g_object (connection, path, object);
        return object;
}

int main (int argc, char **argv)
{
        guint   result;
        GError *error = NULL;
        GMainLoop *loop;
        DBusGConnection *connection;
        DBusGProxy *proxy;

        g_type_init ();
        
        if (!g_thread_supported ())
                g_thread_init (NULL);

        dbus_g_thread_init ();

        loop = g_main_loop_new (NULL, FALSE);
        connection = dbus_g_bus_get (DBUS_BUS_SESSION, &error);
        proxy = dbus_g_proxy_new_for_name (connection,
                DBUS_SERVICE_DBUS,
                DBUS_PATH_DBUS,
                DBUS_INTERFACE_DBUS);


        org_freedesktop_DBus_request_name (proxy,
                M_DBUS_TEST_SERVICE, 
                DBUS_NAME_FLAG_DO_NOT_QUEUE, &result, &error);

        dbus_register_object (connection, 
                proxy,
                M_TYPE_TEST_SERVICE,
                &dbus_glib_m_test_service_object_info,
                M_DBUS_TEST_SERVICE_PATH);

        g_main_loop_run (loop);
}

The TestService object

$EDITOR m-test-service.h

#ifndef _M_TEST_SERVICE_H_
#define _M_TEST_SERVICE_H_

#include <glib.h>
#include <dbus/dbus-glib.h>
#include <dbus/dbus-glib-bindings.h>
#include <dbus/dbus-glib-lowlevel.h>

#define M_DBUS_TEST_SERVICE_PATH  "/org/hello/TestService"
#define M_DBUS_TEST_SERVICE       "org.hello.TestService"
        
#define M_TYPE_TEST_SERVICE            (m_test_service_get_type ())
#define M_TEST_SERVICE(object)         (G_TYPE_CHECK_INSTANCE_CAST ((object), M_TYPE_TEST_SERVICE, MTestService))
#define M_TEST_SERVICE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), M_TYPE_TEST_SERVICE, MTestServiceClass))
#define M_IS_TEST_SERVICE(object)      (G_TYPE_CHECK_INSTANCE_TYPE ((object), M_TYPE_TEST_SERVICE))
#define M_IS_TEST_SERVICE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), M_TYPE_TEST_SERVICE))
#define M_TEST_SERVICE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), M_TYPE_TEST_SERVICE, MTestServiceClass))

G_BEGIN_DECLS

typedef struct _MTestService MTestService;
typedef struct _MTestServiceClass MTestServiceClass;

struct _MTestService {
        GObject parent;
};

struct _MTestServiceClass {
        GObjectClass parent;
};

void m_test_service_ping (MTestService *object, DBusGMethodInvocation *context);

MTestService *m_test_service_new (void);
GType m_test_service_get_type (void);


G_END_DECLS

#endif

$EDITOR m-test-service.c

#include "m-test-service.h"

G_DEFINE_TYPE(MTestService, m_test_service, G_TYPE_OBJECT)

static void
m_test_service_finalize (GObject *object)
{
        G_OBJECT_CLASS (m_test_service_parent_class)->finalize (object);
}


static void
m_test_service_class_init (MTestServiceClass *klass)
{
        GObjectClass *object_class;
        object_class = G_OBJECT_CLASS (klass);
        object_class->finalize = m_test_service_finalize;
}

static void
m_test_service_init (MTestService *object)
{
}


MTestService *
m_test_service_new (void)
{
        return g_object_new (M_TYPE_TEST_SERVICE, NULL);
}

void 
m_test_service_ping (MTestService *object, DBusGMethodInvocation *context)
{
        GError *error = NULL;
        gchar *pong_str = g_strdup ("pong");

        // ...

        if (error) {
                dbus_g_method_return_error (context, error);
                g_error_free (error);
        } else
                dbus_g_method_return (context, pong_str);
        g_free (pong_str);
}

Dispatching to a worker queue

In case you want to deal with the work of your remote API later (to be truly asynchronously), you can do something like this. Note that you must provide a mechanism to throw the execution of a task to the background yourself. Take a look at OAsyncWorker (repo, site), at GAsyncQueue and/or at GThreadPool. There are probably a few other existing solutions too.

$EDITOR configure.ac

...
PKG_CHECK_MODULES(PROG, glib-2.0 gthread-2.0 
        dbus-1 >= $DBUS_REQUIRED 
        dbus-glib-1 >= $DBUS_REQUIRED
        oasyncworker-1.0)
...

$EDITOR m-test-service.c

#include <oasyncworker/oasyncworker.h>

...

typedef struct {
        DBusGMethodInvocation *context;
        GObject *object;
} PingWorkerArguments;

static gpointer 
ping_worker (OAsyncWorkerTask *task, gpointer arguments)
{
        return g_strdup ("pong");
}

static void
ping_callback (OAsyncWorkerTask *task, gpointer func_result)
{
        PingWorkerArguments *info = o_async_worker_task_get_arguments (task);
        gchar *pong_str = func_result;

        dbus_g_method_return (info->context, pong_str);
        g_free (pong_str);

        g_object_unref (info->object);
        g_slice_free (PingWorkerArguments, info);
}

static OAsyncWorker *queue = NULL;

void 
m_test_service_ping (MTestService *object, DBusGMethodInvocation *context)
{
        PingWorkerArguments *info = g_slice_new (PingWorkerArguments);
        OAsyncWorkerTask *task = o_async_worker_task_new ();

        if (!queue)
                queue = o_async_worker_new ();

        info->context = context;
        info->object = g_object_ref (object);

        o_async_worker_task_set_arguments (task, info);
        o_async_worker_task_set_func (task, ping_worker);
        o_async_worker_task_set_callback (task, ping_callback);

        o_async_worker_add (queue, task);

        g_object_unref (task);

        return;
}

Attic/DBusGlibBindings (last edited 2013-11-22 22:34:24 by WilliamJonMcCann)