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'回车退出。
$ ./smssservice is runningnumber=10987654321contents=hello worldcsm_num=8alphabet=gsmcsm_seq=2h#       s       send signal        q       Quits# q$ ./smsch#       s       send message        q       Quits# SendMessage return 11Received message with addree "12345678901" and it says: hello signal!csm_num=3alphabet=ucs2csm_seq=1q
我们可以看到打印出来的信号和消息。对于同一件事情,不同的层次的观察者会看到不同的细节,下表是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/shtouch `find .`aclocalautoconfautoheadertouch NEWS README AUTHORS ChangeLogautomake --add-missing$ cat Makefile.amSUBDIRS = srcEXTRA_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实例讲解》到此结束。其实我的所有文章只是希望能让这复杂的世界简单一点。
转载于:http://www.fmddlmyy.cn/mytext.html 版权归原作者所有。