在dbus中怎样处理复杂的数据类型?第一个建议是尽量不要使用复杂的数据类型。但如果确实需要呢?有的网友建议用GArray作为容器,不管什么参数,在客户端都手工放入GArray,在服务器端再自己取出来。这确实是个思路,比较适合服务器和客户端都是自己开发的情况。还有一篇"How to pass a variant with dbus-glib" 介绍了怎样用GValue传递复杂的数据类型,读者可以参考。
下面看看在我们的例子中是怎样处理a{sv}参数的:
$ cat sms_features.h #ifndef SMS_FEATURES_H #define SMS_FEATURES_H #include <glib-object.h> GHashTable *sms_create_features(const char * alphabet, int csm_num, int csm_seq); GType sms_get_features_type(void); void sms_release_features(GHashTable *features); void sms_show_features(GHashTable *features); #endif
sms_features.h声明了几个函数。这个例子的服务器、客户端都会调用。以下是这些函数的实现:
$ cat -n sms_features.c 1 #include "sms_features.h" 2 3 static void release_val(gpointer data) 4 { 5 GValue *val = (GValue *)data; 6 g_value_unset(val); 7 g_free(val); 8 } 9 10 GHashTable *sms_create_features(const char * alphabet, int csm_num, int csm_seq) 11 { 12 GHashTable *hash; 13 GValue *val; 14 15 hash = g_hash_table_new_full (g_str_hash, NULL, NULL, release_val); 16 17 val = g_new0(GValue, 1); 18 g_value_init (val, G_TYPE_STRING); 19 g_value_set_string (val, alphabet); 20 g_hash_table_insert(hash, "alphabet", val); 21 22 val = g_new0(GValue, 1); 23 g_value_init (val, G_TYPE_INT); 24 g_value_set_int (val, csm_num); 25 g_hash_table_insert(hash, "csm_num", val); 26 27 val = g_new0(GValue, 1); 28 g_value_init (val, G_TYPE_INT); 29 g_value_set_int (val, csm_seq); 30 g_hash_table_insert(hash, "csm_seq", val); 31 32 return hash; 33 } 34 35 GType sms_get_features_type(void) 36 { 37 return dbus_g_type_get_map("GHashTable", G_TYPE_STRING, G_TYPE_VALUE); 38 } 39 40 void sms_show_features(GHashTable *features) 41 { 42 GList *keys = g_hash_table_get_keys(features); 43 gint len = g_list_length(keys); 44 gint i; 45 46 for (i = 0; i < len; i++) { 47 gchar *key = g_list_nth_data(keys, i); 48 GValue *val = g_hash_table_lookup(features, key); 49 50 g_print("%s=", key); 51 switch (G_VALUE_TYPE(val)) { 52 case G_TYPE_STRING: 53 g_print("%s\n", g_value_get_string(val)); 54 break; 55 case G_TYPE_INT: 56 g_print("%d\n", g_value_get_int(val)); 57 break; 58 default: 59 g_print("Value is of unmanaged type!\n"); 60 } 61 } 62 63 g_list_free(keys); 64 } 65 66 void sms_release_features(GHashTable *features) 67 { 68 g_hash_table_destroy(features); 69 } 70
sms_get_features_type调用dbus_g_type_get_map创建a{sv}类型。服务器在创建信号时用到。客户端在调用方法和注册信号时都会用到。 sms_create_features调用g_hash_table_new_full创建哈希表,在创建的同时登记了值对象的清理函数。在sms_release_features调用g_hash_table_destroy销毁哈希表时,创建时登记的值对象清理函数会被调用。
客户端程序如下:
$ cat -n smsc.c 1 #include <dbus/dbus-glib.h> 2 #include <stdio.h> 3 #include <stdlib.h> 4 #include <string.h> 5 #include <glib/giochannel.h> 6 #include "sms-marshal.h" 7 #include "sms_features.h" 8 9 #define SMSC_DEBUG 10 11 static void lose (const char *str, ...) 12 { 13 va_list args; 14 va_start (args, str); 15 vfprintf (stderr, str, args); 16 fputc ('\n', stderr); 17 va_end (args); 18 exit (1); 19 } 20 21 static void lose_gerror (const char *prefix, GError *error) 22 { 23 if (error) { 24 lose ("%s: %s", prefix, error->message); 25 } 26 else { 27 lose ("%s", prefix); 28 } 29 } 30 31 static void incoming_message_handler (DBusGProxy *proxy, const char *address, const char *contents, GHashTable *features, gpointer user_data) 32 { 33 printf ("Received message with addree \"%s\" and it says: \n%s\n", address, contents); 34 sms_show_features(features); 35 } 36 37 static void send_message(DBusGProxy *remote_object) 38 { 39 GError *error = NULL; 40 GHashTable *features; 41 int ret; 42 43 features = sms_create_features ("gsm", 8, 2); 44 printf("SendMessage "); 45 46 if (!dbus_g_proxy_call (remote_object, "SendMessage", &error, 47 G_TYPE_STRING, "10987654321", G_TYPE_STRING, "hello world", 48 sms_get_features_type(), features, G_TYPE_INVALID, 49 G_TYPE_INT, &ret, G_TYPE_INVALID)) 50 lose_gerror ("Failed to complete SendMessage", error); 51 52 printf("return %d\n", ret); 53 sms_release_features(features); 54 } 55 56 static void shell_help(void) 57 { 58 printf( "\ts\tsend message\n" 59 "\tq\tQuit\n" 60 ); 61 } 62 63 #define STDIN_BUF_SIZE 1024 64 static gboolean channel_cb(GIOChannel *source, GIOCondition condition, gpointer data) 65 { 66 int rc; 67 char buf[STDIN_BUF_SIZE+1]; 68 DBusGProxy *remote_object = (DBusGProxy *)data; 69 70 if (condition != G_IO_IN) { 71 return TRUE; 72 } 73 74 /* we've received something on stdin. */ 75 printf("# "); 76 rc = fscanf(stdin, "%s", buf); 77 if (rc <= 0) { 78 printf("NULL\n"); 79 return TRUE; 80 } 81 82 if (!strcmp(buf, "h")) { 83 shell_help(); 84 } else if (!strcmp(buf, "?")) { 85 shell_help(); 86 } else if (!strcmp(buf, "s")) { 87 send_message(remote_object); 88 } else if (!strcmp(buf, "q")) { 89 exit(0); 90 } else { 91 printf("Unknown command `%s'\n", buf); 92 } 93 return TRUE; 94 } 95 96 int main (int argc, char **argv) 97 { 98 DBusGConnection *bus; 99 DBusGProxy *remote_object; 100 GError *error = NULL; 101 GMainLoop *mainloop; 102 GIOChannel *chan; 103 guint source; 104 GType features_type; 105 106 #ifdef SMSC_DEBUG 107 g_slice_set_config(G_SLICE_CONFIG_ALWAYS_MALLOC, TRUE); 108 #endif 109 g_type_init (); 110 mainloop = g_main_loop_new (NULL, FALSE); 111 112 bus = dbus_g_bus_get (DBUS_BUS_SESSION, &error); 113 if (!bus) 114 lose_gerror ("Couldn't connect to session bus", error); 115 116 remote_object = dbus_g_proxy_new_for_name (bus, "org.freesmartphone.ogsmd", 117 "/org/freesmartphone/GSM/Device", 118 "org.freesmartphone.GSM.SMS"); 119 if (!remote_object) 120 lose_gerror ("Failed to get name owner", NULL); 121 122 features_type = sms_get_features_type(); 123 dbus_g_object_register_marshaller (sms_marshal_VOID__STRING_STRING_BOXED, G_TYPE_NONE, G_TYPE_STRING, G_TYPE_STRING, 124 features_type, G_TYPE_INVALID); 125 dbus_g_proxy_add_signal (remote_object, "IncomingMessage", G_TYPE_STRING, G_TYPE_STRING, features_type, G_TYPE_INVALID); 126 dbus_g_proxy_connect_signal (remote_object, "IncomingMessage", G_CALLBACK (incoming_message_handler), NULL, NULL); 127 128 chan = g_io_channel_unix_new(0); 129 source = g_io_add_watch(chan, G_IO_IN, channel_cb, remote_object); 130 g_main_loop_run (mainloop); 131 exit (0); 132 }
112行连接会话总线。116-118行在会话总线上获取连接"org.freesmartphone.ogsmd"的对象"/org/freesmartphone/GSM/Device" 的接口"org.freesmartphone.GSM.SMS"的接口代理对象。
123行调用dbus_g_object_register_marshaller向dbus-glib登记列集函数。 125行调用dbus_g_proxy_add_signal增加对信号IncomingMessage的监听。126行登记信号IncomingMessage的回调函数。 123行登记的还是我们用glib-genmarshal生成的函数sms_marshal_VOID__STRING_STRING_BOXED。 dbus-glib使用这个函数从signal消息中取出信号参数,传递给回调函数,即执行散集操作。这说明glib-genmarshal生成的列集函数既可以用于列集,也可以用于散集。
客户端程序同样用IO Channel接受用户输入。129行在登记回调函数时将指向接口代理对象的指针作为参数传入。回调函数channel_cb在用户键入's'命令后通过send_message函数调用org.freesmartphone.GSM.SMS接口对象的SendMessage方法。 107行的设置G_SLICE_CONFIG_ALWAYS_MALLOC同样是为了用valgrind检查内存泄漏。
我们先运行 dbus-monitor,然后运行smss,再运行smsc。先在smsc中键入's'回车调用SendMessage方法。然后在smss中键入's'回车发送IncomingMessage信号。然后在smsc中键入'q'回车退出。最后在smss中键入'q'回车退出。
$ ./smss service is running number=10987654321 contents=hello world csm_num=8 alphabet=gsm csm_seq=2 h # s send signal q Quit s # q
$ ./smsc h # s send message q Quit s # SendMessage return 11 Received message with addree "12345678901" and it says: hello signal! csm_num=3 alphabet=ucs2 csm_seq=1 q
我们可以看到打印出来的信号和消息。对于同一件事情,不同的层次的观察者会看到不同的细节,下表是dbus-monitor看到的东西:
smss连接会话总线。会话总线发NameOwnerChanged信号,通知唯一名":1.21"被分配。 | signal sender=org.freedesktop.DBus -> dest=(null destination) path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=NameOwnerChanged string ":1.21" string "" string ":1.21" |
smss向会话总线发送Hello取得自己的唯一名":1.21"。 | method call sender=:1.21 -> dest=org.freedesktop.DBus path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=Hello |
smss调用AddMatch要求接收会话总线的NameOwnerChanged信号。 | method call sender=:1.21 -> dest=org.freedesktop.DBus path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=AddMatch string "type='signal',sender='org.freedesktop.DBus',path='/org/freedesktop/DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged'" |
smss调用AddMatch要求接收会话总线发送的所有信号。 | method call sender=:1.21 -> dest=org.freedesktop.DBus path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=AddMatch string "type='signal',sender='org.freedesktop.DBus',path='/',interface='org.freedesktop.DBus'" |
smss调用GetNameOwner获取连接"org.freedesktop.DBus"的唯一名。 | method call sender=:1.21 -> dest=org.freedesktop.DBus path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=GetNameOwner string "org.freedesktop.DBus" |
会话总线发送NameOwnerChanged信号,通知唯一名为":1.21"的连接获得了公众名"org.freesmartphone.ogsmd"。 | signal sender=org.freedesktop.DBus -> dest=(null destination) path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=NameOwnerChanged string "org.freesmartphone.ogsmd" string "" string ":1.21" |
smss请求公众名"org.freesmartphone.ogsmd"。分配公众名在前,请求公众名在后,应该是监控过程颠倒了消息次序。 | method call sender=:1.21 -> dest=org.freedesktop.DBus path=/; interface=org.freedesktop.DBus; member=RequestName string "org.freesmartphone.ogsmd" uint32 0 |
smsc连接会话总线。会话总线发NameOwnerChanged信号,通知唯一名":1.22"被分配。 | signal sender=org.freedesktop.DBus -> dest=(null destination) path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=NameOwnerChanged string ":1.22" string "" string ":1.22" |
smss向会话总线发送Hello取得自己的唯一名":1.22"。 | method call sender=:1.22 -> dest=org.freedesktop.DBus path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=Hello |
smsc调用AddMatch要求接收会话总线的NameOwnerChanged信号。 | method call sender=:1.22 -> dest=org.freedesktop.DBus path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=AddMatch string "type='signal',sender='org.freedesktop.DBus',path='/org/freedesktop/DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged'" |
smsc调用AddMatch要求接收连接'org.freesmartphone.ogsmd'中对象'/org/freesmartphone/GSM/Device'的'org.freesmartphone.GSM.SMS'接口的信号。 | method call sender=:1.22 -> dest=org.freedesktop.DBus path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=AddMatch string "type='signal',sender='org.freesmartphone.ogsmd',path='/org/freesmartphone/GSM/Device',interface='org.freesmartphone.GSM.SMS'" |
smsc调用GetNameOwner获取连接"org.freesmartphone.ogsmd"的唯一名。 | method call sender=:1.22 -> dest=org.freedesktop.DBus path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=GetNameOwner string "org.freesmartphone.ogsmd" |
smsc调用连接'org.freesmartphone.ogsmd'中对象'/org/freesmartphone/GSM/Device'的'org.freesmartphone.GSM.SMS'接口的SendMessage方法。 | method call sender=:1.22 -> dest=org.freesmartphone.ogsmd path=/org/freesmartphone/GSM/Device; interface=org.freesmartphone.GSM.SMS; member=SendMessage string "10987654321" string "hello world" array [ dict entry( string "csm_seq" variant int32 2 ) dict entry( string "alphabet" variant string "gsm" ) dict entry( string "csm_num" variant int32 8 ) ] |
smss向smsc发送method return消息,返回SendMessage方法的输出参数。 | method return sender=:1.21 -> dest=:1.22 reply_serial=5 int32 11 |
smss发送IncomingMessage信号。 | signal sender=:1.21 -> dest=(null destination) path=/org/freesmartphone/GSM/Device; interface=org.freesmartphone.GSM.SMS; member=IncomingMessage string "12345678901" string "hello signal!" array [ dict entry( string "csm_seq" variant int32 1 ) dict entry( string "alphabet" variant string "ucs2" ) dict entry( string "csm_num" variant int32 3 ) ] |
会话总线通知连接":1.22",即smsc的连接已经切断。 | signal sender=org.freedesktop.DBus -> dest=(null destination) path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=NameOwnerChanged string ":1.22" string ":1.22" string "" |
会话总线通知拥有公共名"org.freesmartphone.ogsmd"的连接已经切断。 | signal sender=org.freedesktop.DBus -> dest=(null destination) path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=NameOwnerChanged string "org.freesmartphone.ogsmd" string ":1.21" string "" |
会话总线通知拥有唯一名":1.21"的连接已经切断。即smss已经终止。 | signal sender=org.freedesktop.DBus -> dest=(null destination) path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=NameOwnerChanged string ":1.21" string ":1.21" string "" |
我提供下载的文件要用make distcheck制作的,其中包含了一些自动生成的文件。执行./clean.sh可以删掉自动生成的文件,只留下我创建的文件:
$ find . -type f ./clean.sh ./Makefile.am ./autogen.sh ./src/gsm_sms.h ./src/Makefile.am ./src/sms-marshal.list ./src/smss.xml ./src/smss.c ./src/gsm_sms.c ./src/sms_features.h ./src/sms_features.c ./src/smsc.c ./configure.ac
前面已经介绍过所有的源文件。我们再看看工程文件:
$ cat autogen.sh #! /bin/sh touch `find .` aclocal autoconf autoheader touch NEWS README AUTHORS ChangeLog automake --add-missing $ cat Makefile.am SUBDIRS = src EXTRA_DIST = autogen.sh clean.sh
autogen.sh建立工程环境。在执行clean.sh后,执行autogen.sh重新生成configure等工程文件。其中的touch命令是为了防止文件有将来的时间戳。因为我在虚拟机中运行ubuntu,所以可能会出现这类问题。 Makefile.am将autogen.sh clean.sh也作为发布文件。最重要的工程文件是"configure.ac"和"src/Makefile.am"。
$ cat -n configure.ac 1 AC_INIT() 2 AM_INIT_AUTOMAKE(hello-dbus5, 0.1) 3 AM_CONFIG_HEADER(config.h) 4 5 AC_PROG_CC 6 7 8 # Dbus detection 9 PKG_CHECK_MODULES(DBUS, dbus-1 >= 1.1, have_dbus=yes, have_dbus=no) 10 11 if test x$have_dbus = xno ; then 12 AC_MSG_ERROR([DBus development libraries not found]) 13 fi 14 AM_CONDITIONAL(HAVE_DBUS, test x$have_dbus = xyes) 15 16 AC_SUBST(DBUS_CFLAGS) 17 AC_SUBST(DBUS_LIBS) 18 19 20 # Glib detection 21 PKG_CHECK_MODULES(DBUS_GLIB, gobject-2.0 >= 2.6, have_glib=yes, have_glib=no) 22 23 if test x$have_glib = xno ; then 24 AC_MSG_ERROR([GLib development libraries not found]) 25 fi 26 27 AM_CONDITIONAL(HAVE_GLIB, test x$have_glib = xyes) 28 29 AC_SUBST(DBUS_GLIB_CFLAGS) 30 AC_SUBST(DBUS_GLIB_LIBS) 31 32 33 AC_OUTPUT([Makefile 34 src/Makefile])
8-17行检查dbus库,它们会生成编译常数DBUS_CFLAGS和DBUS_LIBS。 20-30行检查dbus-glib库,它们会生成编译常数DBUS_GLIB_CFLAGS和DBUS_GLIB_LIBS。
$ cat -n src/Makefile.am 1 INCLUDES = \ 2 $(DBUS_CFLAGS) \ 3 $(DBUS_GLIB_CFLAGS) \ 4 -DDBUS_COMPILATION 5 6 LIBS = \ 7 $(DBUS_LIBS) \ 8 $(DBUS_GLIB_LIBS) \ 9 -ldbus-glib-1 10 11 # smss 12 noinst_PROGRAMS = smss 13 14 BUILT_SOURCES = smss-glue.h sms-marshal.h sms-marshal.c 15 smss_SOURCES = $(BUILT_SOURCES) smss.c gsm_sms.c sms_features.c 16 noinst_HEADERS = gsm_sms.h sms_features.h 17 18 19 smss-glue.h: smss.xml 20 $(LIBTOOL) --mode=execute dbus-binding-tool --prefix=gsm_sms --mode=glib-server --output=smss-glue.h $(srcdir)/smss.xml 21 22 sms-marshal.h: sms-marshal.list 23 $(LIBTOOL) --mode=execute glib-genmarshal --header sms-marshal.list --prefix=sms_marshal > sms-marshal.h 24 25 sms-marshal.c: sms-marshal.list 26 $(LIBTOOL) --mode=execute glib-genmarshal --body sms-marshal.list --prefix=sms_marshal > sms-marshal.c 27 28 CLEANFILES = $(BUILT_SOURCES) 29 30 EXTRA_DIST = smss.xml sms-marshal.list 31 32 # smss 33 noinst_PROGRAMS += smsc 34 smsc_SOURCES= smsc.c sms-marshal.c sms_features.c
19-20行由接口描述文件smss.xml生成存根文件smss-glue.h。22-26行由列集接口定义生成包含列集函数的代码。
本文介绍了一个简单的dbus-glib的例子,包括服务器和客户端。第一讲中还有一个加法例子,如果你理解了本文的例子,那个例子就更简单了。 dbus-glib源代码中有两个例子:
好了,《dbus实例讲解》到此结束。其实我的所有文章只是希望能让这复杂的世界简单一点。