1. DBus-glib with org.freedesktop.DBus.GLib.Async
Contents
Note: The example code is outdated. It needs to be converted from dbus-glib to GIO's gdbus.
1.1. Download the samples
1.2. Use Vala to make all this a lot more easy
1.3. First of all
If you are a vi user:
export $EDITOR vi
If you are a emacs user:
export $EDITOR emacs
1.4. 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
1.5. 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>
1.6. Preparing the .service file
$EDITOR program.service.in
[D-BUS Service] Name=org.hello.TestService Exec=@bindir@/program
1.7. 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)|" $< > $@
1.8. 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
1.8.1. 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
1.8.2. 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); }
1.8.3. 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); }
1.8.4. 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; }