使用dbus-glib
dbus-glib是dbus底层接口的一个封装。本讲我们用dbus-glib做一个dus接口,并写一个客户程序。
1、接口
1.1、编写接口描述文件
首先编写接口描述文件。我们要实现的连接的公共名是"org.freesmartphone.ogsmd",接口描述文件如下:
$ cat smss.xml <?xml version="1.0" encoding="UTF-8" ?> <node name="/org/freesmartphone/GSM/Device"> <interface name="org.freesmartphone.GSM.SMS"> <method name="SendMessage"> <arg name="number" type="s"/> <arg name="contents" type="s"/> <arg name="featuremap" type="a{sv}"/> <arg type="i" direction="out"/> </method> <signal name="IncomingMessage"> <arg name="address" type="s"/> <arg name="contents" type="s"/> <arg name="features" type="a{sv}"/> </signal> </interface> </node>
我们要在连接"org.freesmartphone.ogsmd"中实现对象"/org/freesmartphone/GSM/Device"。这个对象有接口"org.freesmartphone.GSM.SMS"。这个接口有一个SendMessage方法和一个IncomingMessage信号。
SendMessage方法和IncomingMessage信号除了两个字符串参数外,还有一个a{sv}参数,这是一个哈希表,即python的字典。键-值对的键类型是字符串,值类型是VARIANT。这个接口是openmoko fso接口的一部分。但为简单起见,本例在哈希表部分,只用三个键值。
- 键"alphabet"对应的值类型是字符串。
- 键"csm_num"对应的值类型是INT32。
- 键"csm_seq"对应的值类型是INT32。
请注意方法和信号名应采用单词连写,首字母大写的格式。
1.2、由接口描述文件生成绑定文件
有一个叫dbus-binding-tool的工具,它读入接口描述文件,产生一个绑定文件。这个文件包含了dbus对象的接口信息。在主程序中我们通过dbus_g_object_type_install_info函数向dbus-glib登记对象信息(DBusGObjectInfo结构)。
本例使用了autotool,在Makefile.am中可以这样调用dbus-binding-tool:
smss-glue.h: smss.xml $(LIBTOOL) --mode=execute dbus-binding-tool --prefix=gsm_sms --mode=glib-server --output=smss-glue.h $(srcdir)/smss.xml
"--prefix"参数定义了对象前缀。设对象前缀是$(prefix),则生成的DBusGObjectInfo结构变量名就是dbus_glib_$(prefix)_object_info。绑定文件会为接口方法定义回调函数。回调函数的名称是这样的:首先将xml中的方法名称转换到全部小写,下划线分隔的格式,然后增加前缀"$(prefix)_"。例如:如果xml中有方法SendMessage,绑定文件就会引用一个名称为$(prefix)_send_message的函数。
绑定文件还会为接口方法生成用于散集(Unmarshaling)的函数。在dbus消息中,方法参数是以流格式存在的。该函数将方法参数由数据流还原到glib的数据格式,并传入方法的回调函数。本例中,dbus-binding-tool生成以下的smss-glue.h:
$ cat smss-glue.h /* Generated by dbus-binding-tool; do not edit! */ #ifndef __dbus_glib_marshal_gsm_sms_MARSHAL_H__ #define __dbus_glib_marshal_gsm_sms_MARSHAL_H__ #include <glib-object.h> G_BEGIN_DECLS #ifdef G_ENABLE_DEBUG #define g_marshal_value_peek_boolean(v) g_value_get_boolean (v) #define g_marshal_value_peek_char(v) g_value_get_char (v) #define g_marshal_value_peek_uchar(v) g_value_get_uchar (v) #define g_marshal_value_peek_int(v) g_value_get_int (v) #define g_marshal_value_peek_uint(v) g_value_get_uint (v) #define g_marshal_value_peek_long(v) g_value_get_long (v) #define g_marshal_value_peek_ulong(v) g_value_get_ulong (v) #define g_marshal_value_peek_int64(v) g_value_get_int64 (v) #define g_marshal_value_peek_uint64(v) g_value_get_uint64 (v) #define g_marshal_value_peek_enum(v) g_value_get_enum (v) #define g_marshal_value_peek_flags(v) g_value_get_flags (v) #define g_marshal_value_peek_float(v) g_value_get_float (v) #define g_marshal_value_peek_double(v) g_value_get_double (v) #define g_marshal_value_peek_string(v) (char*) g_value_get_string (v) #define g_marshal_value_peek_param(v) g_value_get_param (v) #define g_marshal_value_peek_boxed(v) g_value_get_boxed (v) #define g_marshal_value_peek_pointer(v) g_value_get_pointer (v) #define g_marshal_value_peek_object(v) g_value_get_object (v) #else /* !G_ENABLE_DEBUG */ /* WARNING: This code accesses GValues directly, which is UNSUPPORTED API. * Do not access GValues directly in your code. Instead, use the * g_value_get_*() functions */ #define g_marshal_value_peek_boolean(v) (v)->data[0].v_int
在包含绑定文件前,我们必须声明绑定文件要引用的回调函数。
2 对象
2.1 对象定义
dbus-glib用GObject实现dbus对象。所以我们首先要实现一个对象。在本例中,我们实现一个GsmSms对象,它继承了GObject:
$ cat gsm_sms.h #ifndef GSM_SMS_H #define GSM_SMS_H typedef struct GsmSms GsmSms; typedef struct GsmSmsClass GsmSmsClass; struct GsmSms { GObject parent; }; struct GsmSmsClass { GObjectClass parent; }; #define GSM_SMS_TYPE (gsm_sms_get_type ()) GType gsm_sms_get_type (void); gboolean gsm_sms_send_message (GsmSms *obj, const char *number, const char *contents, GHashTable *featuremap, int *ret, GError **error); void gsm_sms_emit_incoming_message(GsmSms *obj, const char * address, const char * contents, GHashTable *hash); #endif
GObject的对象定义虽然繁琐,但有固定的套路。依样画葫芦,画多了就习惯了。我们在gsm_sms.h中声明了gsm_sms_send_message函数。 gsm_sms_send_message函数是在gsm_sms.c中实现的,在绑定文件smss-glue.h中用到。因为主程序要使用绑定文件中的对象信息,所以应由主程序包含绑定文件。主程序只要在包含绑定文件前包含gsm_sms.h,编译器就不会抱怨gsm_sms_send_message函数未声明。
2.2 信号的列集函数
列集(Marshaling)是将数据从某种格式存为流格式的操作;散集(Unmarshaling)则是列集的反操作,将信息从流格式中还原出来。在绑定文件中,dbus-binding-tool自动生成函数将方法参数从dbus消息中还原出来,即实现了散集。那么我们怎么把信号参数由glib的数据结构转换到消息中的数据流呢?
因为GsmSms对象有一个信号,所以在对象类初始化函数gsm_sms_class_init中,我们要调用g_signal_new创建信号。 g_signal_new要求我们提供一个列集函数。
glib有一些标准的列集函数,在gmarshal.h中定义。例如g_cclosure_marshal_VOID__STRING,这个函数适合只有一个字符串参数的信号。如果gmarshal.h没有提供适合的列集函数,我们可以用一个叫glib-genmarshal的工具自动生成列集函数。后面我们会看到,无论是标准列集函数还是生成的列集函数都是既可以用于列集也可以用于散集,这些函数通常都被称作列集函数。
使用glib-genmarshal前,我们同样要准备一个输入文件:
$ cat sms-marshal.list # see glib-genmarshal(1) for a detailed description of the file format, # possible parameter types are: # VOID indicates no return type, or no extra # parameters. if VOID is used as the parameter # list, no additional parameters may be present. # BOOLEAN for boolean types (gboolean) # CHAR for signed char types (gchar) # UCHAR for unsigned char types (guchar) # INT for signed integer types (gint) # UINT for unsigned integer types (guint) # LONG for signed long integer types (glong) # ULONG for unsigned long integer types (gulong) # ENUM for enumeration types (gint) # FLAGS for flag enumeration types (guint) # FLOAT for single-precision float types (gfloat) # DOUBLE for double-precision float types (gdouble) # STRING for string types (gchar*) # PARAM for GParamSpec or derived types (GParamSpec*) # BOXED for boxed (anonymous but reference counted) types (GBoxed*) # POINTER for anonymous pointer types (gpointer) # OBJECT for GObject or derived types (GObject*) # NONE deprecated alias for VOID # BOOL deprecated alias for BOOLEAN VOID:STRING,STRING,BOXED
我们需要的函数返回类型是VOID,参数是STRING,STRING,BOXED。在Makefile.am中可以这样调用glib-genmarshal:
sms-marshal.h: sms-marshal.list $(LIBTOOL) --mode=execute glib-genmarshal --header sms-marshal.list --prefix=sms_marshal > sms-marshal.h sms-marshal.c: sms-marshal.list $(LIBTOOL) --mode=execute glib-genmarshal --body sms-marshal.list --prefix=sms_marshal > sms-marshal.c
"--prefix"和函数原型决定输出函数名。如果"--prefix=sms_marshal",函数原型是"OID:STRING,STRING,BOXED",生成的列集函数名就必然是sms_marshal_VOID__STRING_STRING_BOXED。
在本例中自动生成的文件内容如下:
$ cat sms-marshal.h #ifndef __sms_marshal_MARSHAL_H__ #define __sms_marshal_MARSHAL_H__ #include <glib-object.h> G_BEGIN_DECLS /* VOID:STRING,STRING,BOXED (sms-marshal.list:24) */ extern void sms_marshal_VOID__STRING_STRING_BOXED (GClosure *closure, GValue *return_value, guint n_param_values, const GValue *param_values, gpointer invocation_hint, gpointer marshal_data); G_END_DECLS #endif /* __sms_marshal_MARSHAL_H__ */ $ cat sms-marshal.c #include <glib-object.h> #ifdef G_ENABLE_DEBUG #define g_marshal_value_peek_boolean(v) g_value_get_boolean (v) #define g_marshal_value_peek_char(v) g_value_get_char (v) #define g_marshal_value_peek_uchar(v) g_value_get_uchar (v) #define g_marshal_value_peek_int(v) g_value_get_int (v) #define g_marshal_value_peek_uint(v) g_value_get_uint (v) #define g_marshal_value_peek_long(v) g_value_get_long (v) #define g_marshal_value_peek_ulong(v) g_value_get_ulong (v) #define g_marshal_value_peek_int64(v) g_value_get_int64 (v) #define g_marshal_value_peek_uint64(v) g_value_get_uint64 (v) #define g_marshal_value_peek_enum(v) g_value_get_enum (v) #define g_marshal_value_peek_flags(v) g_value_get_flags (v) #define g_marshal_value_peek_float(v) g_value_get_float (v) #define g_marshal_value_peek_double(v) g_value_get_double (v) #define g_marshal_value_peek_string(v) (char*) g_value_get_string (v) #define g_marshal_value_peek_param(v) g_value_get_param (v) #define g_marshal_value_peek_boxed(v) g_value_get_boxed (v) #define g_marshal_value_peek_pointer(v) g_value_get_pointer (v) #define g_marshal_value_peek_object(v) g_value_get_object (v) #else /* !G_ENABLE_DEBUG */ /* WARNING: This code accesses GValues directly, which is UNSUPPORTED API. * Do not access GValues directly in your code. Instead, use the * g_value_get_*() functions */ #define g_marshal_value_peek_boolean(v) (v)->data[0].v_int #define g_marshal_value_peek_char(v) (v)->data[0].v_int #define g_marshal_value_peek_uchar(v) (v)->data[0].v_uint #define g_marshal_value_peek_int(v) (v)->data[0].v_int #define g_marshal_value_peek_uint(v) (v)->data[0].v_uint #define g_marshal_value_peek_long(v) (v)->data[0].v_long #define g_marshal_value_peek_ulong(v) (v)->data[0].v_ulong #define g_marshal_value_peek_int64(v) (v)->data[0].v_int64 #define g_marshal_value_peek_uint64(v) (v)->data[0].v_uint64 #define g_marshal_value_peek_enum(v) (v)->data[0].v_long #define g_marshal_value_peek_flags(v) (v)->data[0].v_ulong #define g_marshal_value_peek_float(v) (v)->data[0].v_float #define g_marshal_value_peek_double(v) (v)->data[0].v_double #define g_marshal_value_peek_string(v) (v)->data[0].v_pointer #define g_marshal_value_peek_param(v) (v)->data[0].v_pointer #define g_marshal_value_peek_boxed(v) (v)->data[0].v_pointer #define g_marshal_value_peek_pointer(v) (v)->data[0].v_pointer #define g_marshal_value_peek_object(v) (v)->data[0].v_pointer #endif /* !G_ENABLE_DEBUG */ /* VOID:STRING,STRING,BOXED (sms-marshal.list:24) */ void sms_marshal_VOID__STRING_STRING_BOXED (GClosure *closure, GValue *return_value, guint n_param_values, const GValue *param_values, gpointer invocation_hint, gpointer marshal_data) { typedef void (*GMarshalFunc_VOID__STRING_STRING_BOXED) (gpointer data1, gpointer arg_1, gpointer arg_2, gpointer arg_3, gpointer data2); register GMarshalFunc_VOID__STRING_STRING_BOXED callback; register GCClosure *cc = (GCClosure*) closure; register gpointer data1, data2; g_return_if_fail (n_param_values == 4); if (G_CCLOSURE_SWAP_DATA (closure)) { data1 = closure->data; data2 = g_value_peek_pointer (param_values + 0); } else { data1 = g_value_peek_pointer (param_values + 0); data2 = closure->data; } callback = (GMarshalFunc_VOID__STRING_STRING_BOXED) (marshal_data ? marshal_data : cc->callback); callback (data1, g_marshal_value_peek_string (param_values + 1), g_marshal_value_peek_string (param_values + 2), g_marshal_value_peek_boxed (param_values + 3), data2); }
2.3 对象实现
准备好列集函数后,我们来实现GsmSms。
$ cat -n gsm_sms.c 1 #include <dbus/dbus-glib.h> 2 #include <stdio.h> 3 #include <stdlib.h> 4 #include <string.h> 5 #include "gsm_sms.h" 6 #include "sms-marshal.h" 7 #include "sms_features.h" 8 9 enum 10 { 11 INCOMING_MESSAGE, 12 LAST_SIGNAL 13 }; 14 15 static guint signals[LAST_SIGNAL]; 16 17 G_DEFINE_TYPE(GsmSms, gsm_sms, G_TYPE_OBJECT) 18 19 static void gsm_sms_init (GsmSms *obj) 20 { 21 } 22 23 static void gsm_sms_class_init (GsmSmsClass *klass) 24 { 25 signals[INCOMING_MESSAGE] = g_signal_new ( 26 "incoming_message", 27 G_OBJECT_CLASS_TYPE (klass), 28 G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, 29 0, 30 NULL, NULL, 31 sms_marshal_VOID__STRING_STRING_BOXED, 32 G_TYPE_NONE, 3, G_TYPE_STRING, G_TYPE_STRING, 33 sms_get_features_type()); 34 } 35 36 gboolean gsm_sms_send_message (GsmSms *obj, const char *number, const char *contents, 37 GHashTable *featuremap, int *ret, GError **error) 38 { 39 printf("number=%s/n", number); 40 printf("contents=%s/n", contents); 41 sms_show_features(featuremap); 42 *ret = strlen(contents); 43 return TRUE; 44 } 45 46 void gsm_sms_emit_incoming_message(GsmSms *obj, const char * address, 47 const char * contents, GHashTable *hash) 48 { 49 g_signal_emit (obj, signals[INCOMING_MESSAGE], 0, address, contents, hash); 50 }
在类初始化函数gsm_sms_class_init中,我们调用g_signal_new创建了信号。g_signal_new函数的原型是:
guint g_signal_new (const gchar *signal_name, GType itype, GSignalFlags signal_flags, guint class_offset, GSignalAccumulator accumulator, gpointer accu_data, GSignalCMarshaller c_marshaller, GType return_type, guint n_params, ...);
31行提供了列集函数。32-33行是返回值类型和参数类型。其中第三个参数调用了函数sms_get_features_type(在sms_features.h中声明)。因为a{sv}类型的参数处理起来比较繁琐,我专门写了一个sms_features模块处理,后面会介绍。
在主程序中登记对象信息时,对象信息把SendMessage方法和gsm_sms_send_message函数以及自动生成的散集函数联系起来。当客户程序调用SendMessage方法时,dbus-glib会通过对象信息表格找到回调函数和散集函数,用散集函数从method_call消息中取出参数传入回调函数gsm_sms_send_message。 gsm_sms_send_message调用sms_show_features函数处理a{sv}参数。 sms_show_features也在sms_features模块定义,后面会介绍。
gsm_sms模块提供了一个gsm_sms_emit_incoming_message函数供外部模块调用。调用这个函数可以发射一个信号。在真实环境中,只有外部事件发生后才会发射信号。本例中会有测试代码发射信号。
3 主程序
3.1 登记dbus服务器
下面就是主程序
$ cat -n smss.c 1 #include <dbus/dbus-glib.h> 2 #include <stdio.h> 3 #include <stdlib.h> 4 #include <glib/giochannel.h> 5 #include "gsm_sms.h" 6 #include "smss-glue.h" 7 #include "sms_features.h" 8 9 #define SMSS_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 shell_help(void) 32 { 33 printf( "/ts/tsend signal/n" 34 "/tq/tQuit/n" 35 ); 36 } 37 38 void emit_signal(GsmSms *obj) 39 { 40 GHashTable *features = sms_create_features("ucs2", 3, 1); 41 gsm_sms_emit_incoming_message(obj, "12345678901", "hello signal!", features); 42 sms_release_features(features); 43 } 44 45 #define STDIN_BUF_SIZE 1024 46 static gboolean channel_cb(GIOChannel *source, GIOCondition condition, gpointer data) 47 { 48 int rc; 49 char buf[STDIN_BUF_SIZE+1]; 50 GsmSms *obj = (GsmSms *)data; 51 52 if (condition != G_IO_IN) { 53 return TRUE; 54 } 55 56 /* we've received something on stdin. */ 57 printf("# "); 58 rc = fscanf(stdin, "%s", buf); 59 if (rc <= 0) { 60 printf("NULL/n"); 61 return TRUE; 62 } 63 64 if (!strcmp(buf, "h")) { 65 shell_help(); 66 } else if (!strcmp(buf, "?")) { 67 shell_help(); 68 } else if (!strcmp(buf, "s")) { 69 emit_signal(obj); 70 } else if (!strcmp(buf, "q")) { 71 exit(0); 72 } else { 73 printf("Unknown command `%s'/n", buf); 74 } 75 return TRUE; 76 } 77 78 int main (int argc, char **argv) 79 { 80 DBusGConnection *bus; 81 DBusGProxy *bus_proxy; 82 GError *error = NULL; 83 GsmSms *obj; 84 GMainLoop *mainloop; 85 guint request_name_result; 86 GIOChannel *chan; 87 88 #ifdef SMSS_DEBUG 89 g_slice_set_config(G_SLICE_CONFIG_ALWAYS_MALLOC, TRUE); 90 #endif 91 g_type_init (); 92 93 dbus_g_object_type_install_info (GSM_SMS_TYPE, &dbus_glib_gsm_sms_object_info); 94 95 mainloop = g_main_loop_new (NULL, FALSE); 96 97 bus = dbus_g_bus_get (DBUS_BUS_SESSION, &error); 98 if (!bus) 99 lose_gerror ("Couldn't connect to system bus", error); 100 101 bus_proxy = dbus_g_proxy_new_for_name (bus, "org.freedesktop.DBus", 102 "/", "org.freedesktop.DBus"); 103 104 if (!dbus_g_proxy_call (bus_proxy, "RequestName", &error, 105 G_TYPE_STRING, "org.freesmartphone.ogsmd", 106 G_TYPE_UINT, 0, 107 G_TYPE_INVALID, 108 G_TYPE_UINT, &request_name_result, 109 G_TYPE_INVALID)) 110 lose_gerror ("Failed to acquire org.freesmartphone.ogsmd", error); 111 112 obj = g_object_new (GSM_SMS_TYPE, NULL); 113 dbus_g_connection_register_g_object (bus, "/org/freesmartphone/GSM/Device", G_OBJECT (obj)); 114 115 printf ("service is running/n"); 116 chan = g_io_channel_unix_new(0); 117 g_io_add_watch(chan, G_IO_IN, channel_cb, obj); 118 g_main_loop_run (mainloop); 119 120 exit (0); 121 }
93行调用dbus_g_object_type_install_info登记GsmSms类的接口信息。97行连接会话总线。 101-102行在会话总线上为连接"org.freedesktop.DBus"的"/"对象的接口"org.freedesktop.DBus"建立代理。 104-109行通过接口代理调用"RequestName"方法,请求公共名"org.freesmartphone.ogsmd"。
请求公共名成功后,112行建立GsmSms对象。113行登记GsmSms对象,登记时指定对象路径"/org/freesmartphone/GSM/Device",并传入对象指针。118行进入主循环等待客户消息。
3.2 IO Channel
我想增加一个敲键测试信号发射。但我又必须在glib主循环里等待dbus消息。怎样才能既等待dbus消息,又等待敲键呢?这种情况可以使用glib的IO Channels。glib的IO Channels允许我们在glib主循环等待指定的文件或socket句柄。
要使用IO Channels,首先包含"glib/giochannel.h"。116行用句柄0(即标准输入)创建一个GIOChannel。 117行为我们创建的GIOChannel登记回调函数。我们在回调函数channel_cb中处理敲键,发射信号。
3.3 编译运行
读者可以从这里下载完整的示例程序。下集会介绍本例的autotool工程。目前,我们先编译运行一下,解压后执行:
$ ./configure $ make $ cd src $ ./smss service is running h # s send signal q Quit
键入h后回车,可以看到敲键的帮助信息。
我想找个客户程序测试一下,dbus-send不能发a{sv}这样复杂的参数。我们可以用d-feet测试,或者写个python脚本:
$ cat ./smsc.py #!/usr/bin/env python import dbus bus=dbus.SessionBus() bus_obj=bus.get_object('org.freesmartphone.ogsmd', '/org/freesmartphone/GSM/Device') iface=dbus.Interface(bus_obj, 'org.freesmartphone.GSM.SMS') ret=iface.SendMessage('1234567890', 'hello from python', {'alphabet':'gsm','csm_num':8,'csm_seq':2}) print "SendMessage return %d" % (ret)
执行smsc.py,在服务器端看到:
$ ./smss service is running h # s send signal q Quit number=1234567890 contents=hello from python csm_num=8 alphabet=gsm csm_seq=2
说明服务器可以正常工作。主程序的89行要求glib直接用malloc分配内存,这样用valgrind才能检查到内存泄漏(如果有的话)。我们可以这样运行smss以检查是否有内存泄漏:
$ valgrind --tool=memcheck --leak-check=full ./smss
4、复杂的数据类型
在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销毁哈希表时,创建时登记的值对象清理函数会被调用。
5、客户端
5.1、代码
客户端程序如下:
$ 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检查内存泄漏。
5.2、执行
我们先运行 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 "" |
6、工程
我提供下载的文件要用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"。
6.1、configure.ac
$ 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。
6.2、src/Makefile.am
$ 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行由列集接口定义生成包含列集函数的代码。
7、结束语
本文介绍了一个简单的dbus-glib的例子,包括服务器和客户端。第一讲中还有一个加法例子,如果你理解了本文的例子,那个例子就更简单了。 dbus-glib源代码中有两个例子:
- example-service和example-client演示方法调用。这个例子的接口描述文件中有个参数类型写错了,将(us)写成(ss),运行时会出错。可能作者想演示一下接口定义与代码实现不一致的后果吧。读者可以从这里下载我修改过的代码。
- example-signal-emitter和example-signal-recipient演示信号发射。这个例子中,example-signal-recipient调用example-signal-emitter的方法请求发送信号。实际上信号应该是来自服务器侧的信息。我将其改成在example-signal-emitter中敲键发送信号。读者可以从这里下载我修改过的代码。

浙公网安备 33010602011771号