dbus例項講解(四下):使用dbus-glib
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中敲鍵傳送訊號。讀者可以從這裡下載我修改過的程式碼。
好了,《dbus例項講解》到此結束。其實我的所有文章只是希望能讓這複雜的世界簡單一點。