1. 程式人生 > >DBUS-GLIB:從DBUS文字訊息到函式呼叫背後的機制

DBUS-GLIB:從DBUS文字訊息到函式呼叫背後的機制

之前曾經詳細看過在DBUS GLIB BINDING中本地訊息(Signal)如何對映到DBUS訊息(Signal),最近再次研究DBUS 的GLIB,發現尚遺漏了DBus訊息如何對映成本地方法呼叫的重要一環。此處補上。為了比較通透了解文字訊息到函式呼叫的動態型別繫結實現過程,下載了 DBUS、DBUS-GLIB、以及以Embed EDS的為研究入口。由於不同的版本程式碼可能有差異,下面著重說明其原理。本文記錄僅僅以程式碼閱讀為基礎進行梳理,並未進行詳細的程式執行驗證。

1、 預編譯生成的函式型別結構資訊分析。

DBUS GLIB的用法,首先是使用dbus-binding-tool,將DBus物件介面描述的XML檔案生成動態函式型別繫結的資訊。這裡不詳細的說用法,網上有大量的資料。下面以e-data-book-factory為例看看生成的函式型別結構資訊
vi e-data-book-factory-glue.h

static const DBusGMethodInfo dbus_glib_e_data_book_factory_methods[] = {
  { (GCallback) impl_BookFactory_getBook, dbus_glib_marshal_e_data_book_factory_NONE__STRING_POINTER, 0 },
};

impl_BookFactory_getBook為本地呼叫的業務方法,dbus_glib_marshal_e_data_book_factory_NONE__STRING_POINTER為遠端DBUS文字訊息對映到本地呼叫方法需要用到的反序列化函式。


const DBusGObjectInfo dbus_glib_e_data_book_factory_object_info = {
  0,
  dbus_glib_e_data_book_factory_methods,
  1,
"org.gnome.evolution.dataserver.addressbook.BookFactory/0getBook/0A/0source/0I/0s/0path/0O/0F/0N/0o/0/0/0",
"/0",
"/0"
};

DBusGObjectInfo 與DBusGMethodInfo在./dbus-glib.h中定義:

vi ./dbus-glib.h

typedef struct _DBusGObjectInfo DBusGObjectInfo;
typedef struct _DBusGMethodInfo DBusGMethodInfo;
struct _DBusGMethodInfo
{
  GCallback                 function;
  GClosureMarshal           marshaller;
  int                       data_offset;
};

struct _DBusGObjectInfo
{
  int   format_version;

  const DBusGMethodInfo *method_infos;
  int   n_method_infos;
  const char *data;
  const char *exported_signals;
  const char *exported_properties;
};

這裡補充一句data_offset的用法,_DBusGObjectInfo 中的object->data + method->data_offset獲得的地址,才是對method的描述,請看這個函式(dbus-gobject.c)
static const char *
get_method_data (const DBusGObjectInfo *object,
                 const DBusGMethodInfo *method)
{
  return object->data + method->data_offset;
}
這裡補充一個具有多個DBUS函式呼叫的物件例子,僅僅為了說明 data_offset的用法,清楚則可以跳過。
static const DBusGMethodInfo dbus_glib_e_data_book_methods[] = {
  { (GCallback) impl_AddressBook_Book_open, dbus_glib_marshal_e_data_book_NONE__BOOLEAN_POINTER, 0

},
......

  { (GCallback) impl_AddressBook_Book_close, dbus_glib_marshal_e_data_book_NONE__POINTER, 1275 },
};

const DBusGObjectInfo dbus_glib_e_data_book_object_info = {
  0,
  dbus_glib_e_data_book_methods,
  16,
"org.gnome.evolution.dataserver.addressbook.Book/0open/0A/0only_if_exists/0I/0b/0/0......org.gnome.evolution.dataserver.addressbook.Book/0auth_required/0/0",
"/0"
};

2、 dbus_glib_e_data_book_factory_object_info的資料結構作為型別描述附加資訊在 e_data_book_factory的“類的初始化定義函式“e_data_book_factory_class_init中被記住


static void
e_data_book_factory_class_init (EDataBookFactoryClass *e_data_book_factory_class)
{
        g_type_class_add_private (e_data_book_factory_class, sizeof (EDataBookFactoryPrivate));
        dbus_g_object_type_install_info (G_TYPE_FROM_CLASS (e_data_book_factory_class), &dbus_glib_e_data_book_factory_object_info);
}

vi ./dbus-gobject.c

void
dbus_g_object_type_install_info (GType                  object_type,
                                 const DBusGObjectInfo *info)
{
  g_return_if_fail (G_TYPE_IS_CLASSED (object_type) || G_TYPE_IS_INTERFACE (object_type));

  _dbus_g_value_types_init ();

  g_type_set_qdata (object_type,
                    dbus_g_object_type_dbus_metadata_quark (),
                    (gpointer) info);
}


3、主函式啟動,向DBus註冊e_data_book_factory物件。

        factory = g_object_new (E_TYPE_DATA_BOOK_FACTORY, NULL);
        dbus_g_connection_register_g_object (connection,
                                             "/org/gnome/evolution/dataserver/addressbook/BookFactory",
                                             G_OBJECT (factory));


dbus_g_connection_register_g_object在 dbus-gobject.c中定義,主要邏輯為dbus_connection_register_object_path操作。

3.1  DBusObjectPathVTable的理解
vi ./dbus-gobject.c
    ......
if (!dbus_connection_register_object_path (DBUS_CONNECTION_FROM_G_CONNECTION (connection),
                                             at_path,
                                             &gobject_dbus_vtable,
                                             o))


gobject_dbus_vtable的定義追述如下:

static const DBusObjectPathVTable gobject_dbus_vtable = {
  object_registration_unregistered,
  object_registration_message,
  NULL
};
dbus_connection_register_object_path 用來向DBus連線上特定物件的訊息處理掛接處理函式表,亦即是上面定義的DBusObjectPathVTable。 dbus_connection_register_object_path並不是唯一可以為DBUS連線掛接處理函式的方法,據文件所說 dbus_connection_add_filter也可以,並且優先順序更高。
文件中說:Adds a message filter. Filters are handlers that are run on all incoming messages, prior to the objects registered with dbus_connection_register_object_path().

vi ./dbus/dbus-connection.h 可以看到DBusObjectPathVTable的定義。
struct DBusObjectPathVTable
{
  DBusObjectPathUnregisterFunction   unregister_function; /**< Function to unregister this handler */
  DBusObjectPathMessageFunction      message_function; /**< Function to handle messages */
  void (* dbus_internal_pad1) (void *); /**< Reserved for future expansion */
  void (* dbus_internal_pad2) (void *); /**< Reserved for future expansion */
  void (* dbus_internal_pad3) (void *); /**< Reserved for future expansion */
  void (* dbus_internal_pad4) (void *); /**< Reserved for future expansion */
};

3.2 獲取註冊物件(此處為e-data-book-factory)上的DBusGObjectInfo

DBusObjectPathMessageFunction 是處理物件上每個訊息的回掉函式,在dbus-gobject的定義中, 是object_registration_message。亦即是,對DBUS連線的Object_Path每個訊息到來時,將呼叫 object_registration_message函 數,對每個method的DBUS訊息,需要分析其要求呼叫的函式原型是什麼,並進行呼叫,根據DBUS訊息尋找要求呼叫的函式原型的功能由 lookup_object_and_method來完成。

static DBusHandlerResult
object_registration_message (DBusConnection  *connection,
                             DBusMessage     *message,
                             void            *user_data)
{
....
 DBusMessageIter iter;
  const DBusGMethodInfo *method;
  const DBusGObjectInfo *object_info;
  DBusMessage *ret;
  ObjectRegistration *o;

  o = user_data;
  object = G_OBJECT (o->object);

  if (dbus_message_is_method_call (message,
                                   DBUS_INTERFACE_INTROSPECTABLE,
                                   "Introspect"))
    return handle_introspect (connection, message, object);

  /* Try the metainfo, which lets us invoke methods */
  object_info = NULL;
  if (lookup_object_and_method (object, message, &object_info, &method))
    return invoke_object_method (object, object_info, method, connection, message);
......
}


lookup_object_and_method 和invoke_object_method是其中的重點。


static gboolean
lookup_object_and_method (GObject      *object,
                          DBusMessage  *message,
                          const DBusGObjectInfo **object_ret,
                          const DBusGMethodInfo **method_ret)
{
  const char *interface;
  const char *member;
  const char *signature;
  GList *info_list;
  const GList *info_list_walk;
  const DBusGObjectInfo *info;
  int i;

  interface = dbus_message_get_interface (message);
  member = dbus_message_get_member (message);
  signature = dbus_message_get_signature (message);

  info_list = lookup_object_info (object);

  for (info_list_walk = info_list; info_list_walk != NULL; info_list_walk = g_list_next (info_list_walk))
    {
      info = (DBusGObjectInfo *) info_list_walk->data;
      *object_ret = info;

      for (i = 0; i < info->n_method_infos; i++)
        {
          const char *expected_member;
          const char *expected_interface;
          char *expected_signature;
          const DBusGMethodInfo *method;

          method = &(info->method_infos[i]);

          /* Check method interface/name and input signature */
          expected_interface = method_interface_from_object_info (*object_ret, method);
          expected_member = method_name_from_object_info (*object_ret, method);
          expected_signature = method_input_signature_from_object_info (*object_ret, method);

          if ((interface == NULL
              || strcmp (expected_interface, interface) == 0)
              && strcmp (expected_member, member) == 0
              && strcmp (expected_signature, signature) == 0)
            {
              g_free (expected_signature);
              *method_ret = method;
              g_list_free (info_list);
              return TRUE;
            }
            g_free (expected_signature);
        }
    }

  if (info_list)
    g_list_free (info_list);

  return FALSE;
}

lookup_object_and_method 其呼叫的結果是object_info, method獲取了第一步設定到e-data-book-factory中的DBusGObjectInfo中設定的函式資訊。

下面幾個函式補充說明如何從物件中獲取之前在g_type_set_qdata設定的資料,即是上面提到的DBusGObjectInfo。
static gboolean
lookup_object_info_cb (const DBusGObjectInfo *info,
                       GType gtype,
                       gpointer user_data)
{
  GList **list = (GList **) user_data;

  *list = g_list_prepend (*list, (gpointer) info);
  return TRUE;
}

static GList *
lookup_object_info (GObject *object)
{
  GList *info_list = NULL;

  foreach_object_info (object, lookup_object_info_cb, &info_list);

  return info_list;
}
static void
foreach_object_info (GObject *object,
                     ForeachObjectInfoFn callback,
                     gpointer user_data)
{
  GType *interfaces, *p;
  const DBusGObjectInfo *info;
  GType classtype;

  interfaces = g_type_interfaces (G_TYPE_FROM_INSTANCE (object), NULL);

  for (p = interfaces; *p != 0; p++)
    {
      info = g_type_get_qdata (*p, dbus_g_object_type_dbus_metadata_quark ());
      if (info != NULL && info->format_version >= 0)
        {
          if (!callback (info, *p, user_data))
            break;
        }
    }

  g_free (interfaces);

  for (classtype = G_TYPE_FROM_INSTANCE (object); classtype != 0; classtype = g_type_parent (classtype))
    {
      info = g_type_get_qdata (classtype, dbus_g_object_type_dbus_metadata_quark ());
      if (info != NULL && info->format_version >= 0)
        {
          if (!callback (info, classtype, user_data))
            break;
        }
    }

}

4、找到函式呼叫的原型並呼叫。

呼叫是一個複雜的過程,其中的邏輯包括,將DBUS的訊息中的 函式呼叫引數提取出來,放到value_array的資料結構,處理Dbus同步/非同步返回的邏輯,將value_array提交給 DBusGMethodInfo *method定義的引數處理邏輯(將字串的資料對映成有具體的引數型別),並具體呼叫DBusGMethodInfo *method回撥方法。

static DBusHandlerResult
invoke_object_method (GObject         *object,
                      const DBusGObjectInfo *object_info,
                      const DBusGMethodInfo *method,
                      DBusConnection  *connection,
                      DBusMessage     *message){
......
/* Actually invoke method */
  method->marshaller (&closure, have_retval ? &return_value : NULL,
                      value_array->n_values,
                      value_array->values,
                      NULL, method->function);

......