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;
}