之前曾经详细看过在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);
......
}