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);
......
}