1. 程式人生 > >dbus例項講解(四下):使用dbus-glib

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