DBUS-GLIB:从DBUS文本消息到函数调用背后的机制

时间:2022-06-11 03:36:58

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

......