基于 DBus 的应用程序可以有两种方式实现,一种是使用 DBus Daemon 的总线型结构;另一种是点对点的星型结构。本文我们介绍使用 DBus Daemon 的总线型结构。
1. 概述
DBus 的总线模式是利用 DBus Daemon 系统守护进程完成消息的转发。 GUN 提供了 dbus-glib 和 glib 两个库,大大简化了开发者的开发工作。其中 dbus-glib 中的接口已经被废弃不建议使用,我这里有一个利用 dbus-glib 接口写的例子ueventmonitor 。下面文章我们主要介绍利用 glib 库实现 DBus 总线模式,源码我已上传到 github 。
2. 准备工作
首先引入要使用的库,主要包括两个 glib 和 gio-unix ,这两个库在 CentOS 平台都存在于 glib2-devel 包里。在根目录的 configure.ac 文件中增加如下内容:
PKG_CHECK_MODULES(GLIB, [glib-2.0 >= 2.31.13])
AC_SUBST(GLIB_CFLAGS)
AC_SUBST(GLIB_LIBS)
PKG_CHECK_MODULES(GIO, [gio-unix-2.0 >= 2.31.13])
AC_SUBST(GIO_CFLAGS)
AC_SUBST(GIO_LIBS)
在 Makefile.am 中使用上面的两个库,这样在编译时就去指定目录寻找相应的库文件和头文件。在 Makefile.am 添加如下内容:
AM_CPPFLAGS = \
$(GLIB_CFLAGS) \
$(GIO_CFLAGS) \
$(NULL)
LIBS = \
$(GLIB_LIBS) \
$(GIO_LIBS) \
$(NULL)
提供一个用于生成代码的 XML 文件,这个 XML 在 DBus 中称为 introspection data ,用来描述 DBus 服务提供哪些方法、属性、信号等,例如方法的表示如下所示:
<node name="/">
<interface name="org.freedesktop.Testdbus.Base">
<method name="TestInt">
<arg name="ret" type="i" direction="out" />
</method>
<method name="TestStr">
<arg name="arg0" type="i" direction="in" />
<arg name="ret" type="s" direction="out" />
</method>
<method name="TestStructs">
<arg name="ret" type="a(si)" direction="out" />
</method>
</interface>
</node>
方法可以有输入、输出。方法的名字用驼峰的命名方式命名,参数的名字可以随意,但参数的 type 和 direction 要准确。 type 指参数的类型, direction 指参数的方向, out 代表输出, in 代表输入。 type 类型比较多,如下表所示:
| 类型 | 说明 |
|---|---|
| a | ARRAY 数组 |
| b | BOOLEAN 布尔值 |
| d | DOUBLE IEEE 754双精度浮点数 |
| g | SIGNATURE 类型签名 |
| i | INT32 32位有符号整数 |
| n | INT16 16位有符号整数 |
| o | OBJECT_PATH 对象路径 |
| q | UINT16 16位无符号整数 |
| s | STRING 零结尾的UTF-8字符串 |
| t | UINT64 64位无符号整数 |
| u | UINT32 32位无符号整数 |
| v | VARIANT 可以放任意数据类型的容器,数据中包含类型信息。例如glib中的GValue。 |
| x | INT64 64位有符号整数 |
| y | BYTE 8位无符号整数 |
| () | 定义结构时使用。例如”(i(ii))” |
| {} | 定义键-值对时使用。例如”a{us}” |
| h | 文件描述符 |
将 XML 文件编译生成代码,代码供给 DBus 服务端和客户端使用。将 XML 文件编译成 C 代码需要使用 gdbus-codegen 工具,具体要在 Makefile.am 文件中添加如下内容:
dbus_built_sources = testdbus-generated.h testdbus-generated.c
$(dbus_built_sources) : $(top_srcdir)/data/org.freedesktop.Testdbus.xml
gdbus-codegen --interface-prefix org.freedesktop.Testdbus --c-namespace Testdbus --generate-c-code=testdbus-generated $<
BUILT_SOURCES = \
$(dbus_built_sources) \
$(NULL)
gdbus-codegen 工具将根据 XML 文件生成 testdbus-generated.h 和 testdbus-generated.c 两个文件。这两个文件中创建了 TestdbusBase 对象,该对象的名字是由 –c-namespace 和 –interface-prefix 两个选项决定的。
在编译服务端和客户端程序时将 $(BUILT_SOURCES) 编译进去。在 Makefile.am 文件中添加如下内容:
# 服务端程序
testdbusprivdir = $(libexecdir)/testdbus
testdbuspriv_PROGRAMS = testdbusd
testdbusd_SOURCES = main.c $(BUILT_SOURCES)
# 客户端程序
bin_PROGRAMS = testgdbus
testgdbus_SOURCES = testgdbus.c $(BUILT_SOURCES)
3. 服务端实现
服务端首先创建一个循环 g_main_loop_new ,然后使用 g_bus_own_name 接口想 DBus Daemon 获取 “org.freedesktop.Testdbus” 名字的 DBus ,如果该名字没有被占用,则获取成功,并回调 on_bus_acquired 接口,该接口是调用 g_bus_own_name 函数时传入的。
在 on_bus_acquired 回调函数中创建 TestdbusBase 对象,并注册 XML 内容中的方法,然后将这个 Interface 导出,这样可以在 D-feet 工具中查看并使用。代码如下所示:
skeleton = testdbus_base_skeleton_new ();
g_signal_connect(skeleton, "handle-test-int", G_CALLBACK(on_handle_test_int), NULL);
g_signal_connect(skeleton, "handle-test-str", G_CALLBACK(on_handle_test_str), NULL);
g_signal_connect(skeleton, "handle-test-structs", G_CALLBACK(on_handle_test_structs), NULL);
g_dbus_interface_skeleton_export(G_DBUS_INTERFACE_SKELETON(skeleton), connection,
"/org/freedesktop/Testdbus/Base", &error);
4. 客户端实现
使用 testdbus-generated.h 中提供的接口实现调用服务端进程的方法。首先获取 TestdbusBase 对象的代理,然后通过代理调用服务端的方法。代码如下所示:
proxy = testdbus_base_proxy_new_for_bus_sync(G_BUS_TYPE_SYSTEM,
G_DBUS_PROXY_FLAGS_NONE,
"org.freedesktop.Testdbus",
"/org/freedesktop/Testdbus/Base",
NULL,
&error);
testdbus_base_call_test_int_sync(proxy, &ret, NULL, &error);
5. 返回值处理
客户端调用服务端的方法返回值的类型各异,这里介绍下对于返回指类型是结构体数组的情况。
5.1. 服务端处理
首先创建一个 GVariantBuilder 对象,并初始化该对象存储的参数类型;
然后将参数值一次添加到 GVariantBuilder 对象中;
最终将 GVariantBuilder 对象转换成 GVariant 对象返回;
代码如下所示:
GVariantBuilder builder;
GVariant *ret = NULL;
g_printerr ("Method call:on_handle_test_structs\n");
g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(si)"));
g_variant_builder_add (&builder, "(si)", "test1", 1);
g_variant_builder_add (&builder, "(si)", "test2", 2);
g_variant_builder_add (&builder, "(si)", "test3", 3);
ret = g_variant_builder_end (&builder);
testdbus_base_complete_test_structs(skeleton, invocation, ret);
5.2. 客户端处理
首先客户端得到一个 GVariant 类型的对象,并将其初始化为 GVariantIter 对象;
然后以此遍历 GVariantIter 对象,取出每一个结构体的值;
代码如下所示:
testdbus_base_call_test_structs_sync(proxy, &ret, NULL, &error);
g_variant_iter_init(&iter, ret);
while (g_variant_iter_next(&iter, "(si)", &ret_str, &ret_int)) {
g_printerr("sync reply:%s\n", ret_str);
g_printerr("sync reply:%d\n", ret_int);
g_free(ret_str);
}