DBUS-GLIB Binding,GLIB事件與DBUS事件是如何關聯的

DBus有兩種API接口,一種是直接使用DBUS的 low-level API,一種是使用Binding,Bindings有不同的類型,有PERL Binding、PYTHON Binding、GLIB Binding等。這裏主要關注使用GLIB binding。和low-level API不同的是,GLIB binding則能夠完成GLIB OBJECT的本地事件(native signal)與DBus事件的綁定,下面描述使用DBUS,Signal事件發送和接收的基本過程。

1、Signal被髮送到DBus Daemon,這個過程如果使用low-level API,這個過程需要程序直接完成;如果使用GLIB Binding,則GLIB OBJECT發送本地的Signal時自動完成。這個本地的Signal,就是g_signal_emit方法發出的GLIB OBJECT的Signal。
2、Signal包含接口的標識、Signal的標識、以及發送者的標識、其他參數。
3、DBus上的任何進程可以提交Signal的“過濾規則”。
4、DBus Daemon根據Signal過濾規則,將Signal發送到各個進程。
5、Signal接收進程接到Signal進行處理,如果使用low-level API,則直接處理Signal;如果使用的是GLIB Binding,GLIB Binding將在其代理對象上觸發一個本地事件(emit a native signal )


下面回到DBus-GLIB的例子程序,詳細說明這個過程。
DBus-GLIB Binding的例子運行過程如《初探DBUS(1)》一文所示(如果有疑問需要獲取源碼請參見初探DBUS(1)),在例子主幹邏輯如下:
1、example-signal-recipient程序負責定期向發起
example-signal-emitter程序emitHelloSignal遠程方法調用
2、example-signal-emitter程序爲emitHelloSignal的服務器端,接收到調用後,向example-signal-recipient發送HELLO_SIGNAL的SIGNAL事件
3、
example-signal-recipient接收到事件並打印。

example-signal-emitter程序的分析如下:
(1)從example-signal-emitter.xml文件生成對應的
example-signal-emitter-glue.h文件

example-signal-emitter.xml文件如下:


<?xml version="1.0" encoding="UTF-8" ?>

<node name="/">
  <interface name="org.designfu.TestService">

    <method name="emitHelloSignal">
    </method>
    
    <!-- Mark the signal as exported -->
    <signal name="HelloSignal"/>

  </interface>
</node>

命令行如下:
dbus-binding-tool --prefix=test_object --mode=glib-server --output=example-signal-emitter-glue.h ./example-signal-emitter.xml

example-signal-emitter-
glue.h定義了本地的對象中哪個Signal與DBus的Signal綁定。example-signal-emitter-glue.h原碼中定義了HelloSignal的DBus 的Signal與本地定義Signal綁定
const DBusGObjectInfo dbus_glib_test_object_object_info = {
  0,
  dbus_glib_test_object_methods,
  1,
"org.designfu.TestService/0emitHelloSignal/0S/0/0/0",
"org.designfu.TestService/0HelloSignal/0/0",
"/0"
};

(2)example-signal-emitter.c中定義了一個GOBJECT風格的“對象” TestObject。TestObject的class_init方法定義了hello_signal 的本地Signal。關於GOBJECT的對象模型請查看GOBJECT的相關文檔。
static void test_object_class_init (TestObjectClass *klass)
{
 signals[HELLO_SIGNAL] =
    g_signal_new ("hello_signal",
          G_OBJECT_CLASS_TYPE (klass),
                  G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
                  0,
                  NULL, NULL,
                  g_cclosure_marshal_VOID__STRING,
                  G_TYPE_NONE, 1, G_TYPE_STRING);
}

這裏有一個問題,名爲“HelloSignal”的DBus事件如何同名爲“hello_signal”關聯的?這個問題可以從./dbus-gobject.c的export_signals 函數得到解答,該函數調用了s = _dbus_gutils_wincaps_to_uscore (signame);_dbus_gutils_wincaps_to_uscore這個函數將HelloSignal翻譯成了hello_signal

(3)當example-signal-emitter在接收到emitHelloSignal遠程方法調用中發出"hello_signal"的本地Signal
g_signal_emit (obj, signals[HELLO_SIGNAL], 0, "Hello");

本地的Signal如何觸發DBus的Signal呢?
到回example-signal-emitter的主函數繼續分析
  
(3.1)初始化

  DBusGConnection *bus;
  DBusGProxy *bus_proxy;
  GError *error = NULL;
  TestObject *obj;
  GMainLoop *mainloop;
  guint request_name_result;

  g_type_init ();

  dbus_g_object_type_install_info (TEST_TYPE_OBJECT, &dbus_glib_test_object_object_info);


dbus_glib_test_object_object_info見第(1)步的的說明

(3.2)設置本地Signal與DBus Signal的綁定

  mainloop = g_main_loop_new (NULL, FALSE);

  bus = dbus_g_bus_get (DBUS_BUS_SESSION, &error);
  if (!bus)
    lose_gerror ("Couldn't connect to session bus", error);

  bus_proxy = dbus_g_proxy_new_for_name (bus, "org.freedesktop.DBus",
                     "/org/freedesktop/DBus",
                     "org.freedesktop.DBus");

  if (!dbus_g_proxy_call (bus_proxy, "RequestName", &error,
              G_TYPE_STRING, "org.designfu.TestService",
              G_TYPE_UINT, 0,
              G_TYPE_INVALID,
              G_TYPE_UINT, &request_name_result,
              G_TYPE_INVALID))
    lose_gerror ("Failed to acquire org.designfu.TestService", error);

  obj = g_object_new (TEST_TYPE_OBJECT, NULL);

 
  dbus_g_connection_register_g_object (bus, "/org/designfu/TestService/object", G_OBJECT (obj));

  printf ("test service running/n");

  g_main_loop_run (mainloop);

  exit (0);


dbus_g_connection_register_g_object方法中調用了上文提到的export_signals ,export_signals 對每個export的本地signal進行了如下的操作:

          closure = dbus_g_signal_closure_new (connection, object, signame, (char*) iface);
          g_closure_set_marshal (closure, signal_emitter_marshaller);

          g_signal_connect_closure_by_id (object,
                          id,
                          0,
                          closure,
                          FALSE);

          g_closure_add_finalize_notifier (closure, NULL,
                           dbus_g_signal_closure_finalize);


closure是什麼呢?文檔說“A GClosure represents a callback supplied by the programmer.” g_signal_connect_closure_by_id函數爲每個本地的Signal(參數id,是本地Signal的ID)掛載一個處理回調的closure,closure中關鍵函數是signal_emitter_marshaller

static void
signal_emitter_marshaller (GClosure        *closure,
                           GValue          *retval,
                           guint            n_param_values,
                           const GValue    *param_values,
                           gpointer         invocation_hint,
                           gpointer         marshal_data)
{
  DBusGSignalClosure *sigclosure;
  DBusMessage *signal;
  DBusMessageIter iter;
  guint i;
  const char *path;

  sigclosure = (DBusGSignalClosure *) closure;

  g_assert (retval == NULL);

  path = _dbus_gobject_get_path (sigclosure->object);

  g_assert (path != NULL);

  signal = dbus_message_new_signal (path,
                                    sigclosure->sigiface,
                                    sigclosure->signame);
  if (!signal)
    {
      g_error ("out of memory");
      return;
    }

  dbus_message_iter_init_append (signal, &iter);

  /* First argument is the object itself, and we can't marshall that */
  for (i = 1; i < n_param_values; i++)
    {
      if (!_dbus_gvalue_marshal (&iter,
                                (GValue *) (&(param_values[i]))))
        {
          g_warning ("failed to marshal parameter %d for signal %s",
                     i, sigclosure->signame);
          goto out;
        }
    }
  dbus_connection_send (DBUS_CONNECTION_FROM_G_CONNECTION (sigclosure->connection),
                        signal, NULL);
 out:
  dbus_message_unref (signal);
}

可以很清晰的看到,dbus_connection_send 的過程,於是,本地的signal觸發--〉dbus_g_closure-->signal_emitter_marshaller,在signal_emitter_marshaller將DBus的Signal發送了出去。



-----------------------------------------------------------------------------------------------------------------
example-signal-recipient的主幹邏輯代碼如下:

(1)初始化Session Bus連接,並獲取遠程對象。
  DBusGConnection *bus;
  DBusGProxy *remote_object;
  GError *error = NULL;
  GMainLoop *mainloop;

  g_type_init ();

  mainloop = g_main_loop_new (NULL, FALSE);

  bus = dbus_g_bus_get (DBUS_BUS_SESSION, &error);
  if (!bus)
    lose_gerror ("Couldn't connect to session bus", error);
  
  /* We use _for_name_owner in order to track this particular service
   * instance, which lets us receive signals.
   */
  remote_object = dbus_g_proxy_new_for_name (bus,
                         "org.designfu.TestService",
                         "/org/designfu/TestService/object",
                         "org.designfu.TestService");
  if (!remote_object)
    lose_gerror ("Failed to get name owner", error);


(2)被註釋掉的重要步驟:註冊Signal處理的marshaller。
由 於例子使用的遠程調用方法emitHelloSignal不帶參數,因此可以使用系統內置的marshaller,因此例子程序中將 dbus_g_object_register_marshaller註釋了。這裏參照pidgin 項目(另一個使用DBus的著名開源項目)的DBus用法,將該步驟補上,在pidgin的例子中,Signal的interface名 爲"im.pidgin.purple.PurpleInterface"
(2.1)查看Signal的“函數原型”,使用dbus-monitor命令進行查找:
命令行:dbus-monitor type=signal interface="im.pidgin.purple.PurpleInterface"
結果:
signal sender=:1.21 -> dest=(null destination) path=/im/pidgin/purple/PurpleObject; interface=im.pidgin.purple.PurpleInterface; member=ReceivedImMsg
 int32 1097
 string "[email protected]"
 string "<FONT FACE="Times"><FONT COLOR="#000000">Hi!</FONT></FONT>"
 int32 8728
 uint32 0
(2.2)生成marshal.list文件
內容如下:
VOID:INT,STRING,STRING,INT,UINT

其含義是:返回爲VOID,參數類型按次序爲
INT,STRING,STRING,INT,UINT

(2.3)生成marshal.h和marshal.c
glib-genmarshal --header --prefix=marshal marshal.list > marshal.h
glib-genmarshal --body --prefix=marshal marshal.list > marshal.c

(2.4)註冊marshaller

 dbus_g_object_register_marshaller(marshal_VOID__INT_STRING_STRING_INT_UINT, 
 G_TYPE_NONE, G_TYPE_INT, G_TYPE_STRING, 
 G_TYPE_STRING, G_TYPE_INT, G_TYPE_UINT, 
 G_TYPE_INVALID);


(3)設置需處理的HelloSignal事件

dbus_g_proxy_add_signal (remote_object, "HelloSignal", G_TYPE_STRING, G_TYPE_INVALID);


dbus_g_proxy_connect_signal (remote_object, "HelloSignal", G_CALLBACK (hello_signal_handler),
                   NULL, NULL);
 


(4)設置一個定時器,定期發起
emitHelloSignal調用。
定時器回調方法如下:

static gboolean emit_signal (gpointer arg)
{
  DBusGProxy *proxy = arg;
  
  dbus_g_proxy_call_no_reply (proxy, "emitHelloSignal", G_TYPE_INVALID);
  return TRUE;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章