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實例講解》到此結束。其實我的所有文章只是希望能讓這複雜的世界簡單一點。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章