迁移到 GDBus

迁移到 GDBus

概念性差异

D-Bus 的核心概念在 dbus-glib 和 GDBus 中以非常相似的方式建模。两者都有表示连接、代理和方法调用的对象。但是,还有一些重要的差异

  • dbus-glib 使用 libdbus 引用实现,而 GDBus 不使用。相反,它依赖于 GIO 流作为传输层,并为其 D-Bus 连接设置和认证实现了自己的实现。除了使用流作为传输外,避免 libdbus 还使 GDBus 避免了一些复杂的多线程问题。
  • dbus-glib 使用 GObject 类型系统来表示方法和返回值的参数,包括一个自制的容器特定化机制。GDBus 依赖于 GVariant 类型系统,该系统是明确设计来匹配 D-Bus 类型的。
  • dbus-glib 仅模型化 D-Bus 接口,不提供任何对象的类型。GDBus 既有 D-Bus 接口(通过 GDBusInterface、GDBusProxy 和 GDBusInterfaceSkeleton 类型)也有对象(通过 GDBusObject、GDBusObjectSkeleton 和 GDBusObjectProxy 类型)的模型。
  • GDBus 包含了对 org.freedesktop.DBus.Properties(通过 GDBusProxy 类型)和 org.freedesktop.DBus.ObjectManager D-Bus 接口的原生支持,而 dbus-glib 则没有。
  • 在 dbus-glib 中导出对象的一种典型方法是通过使用 dbus-binding-tool 生成从 XML 反射数据生成的胶水代码。GDBus 提供了一个类似的工具,称为 gdbus-codegen,该工具也可以生成 Docbook D-Bus 接口文档。
  • dbus-glib 不提供任何关于拥有和监视总线名称的便利 API,而 GDBus 提供了一系列便利函数 g_bus_own_name()g_bus_watch_name()
  • GDBus 提供了解析、生成和操作 Introspection XMLAPI,而 dbus-glib 则不提供。
  • GTestDBus 提供了创建隔离单元测试的 API

API 对比

dbus-glib GDBus
DBusGConnection GDBusConnection
DBusGProxy GDBusProxyGDBusInterface - 另见 GDBusObjectProxy
DBusGObject GDBusInterfaceSkeletonGDBusInterface - 另见 GDBusObjectSkeleton
DBusGMethodInvocation GDBusMethodInvocation
dbus_g_bus_get() g_bus_get_sync(),另见 g_bus_get()
dbus_g_proxy_new_for_name() g_dbus_proxy_new_sync()g_dbus_proxy_new_for_bus_sync(),另见 g_dbus_proxy_new()
dbus_g_proxy_add_signal() 不需要,使用通用的“g-signal”
dbus_g_proxy_connect_signal() 使用 g_signal_connect() 与“g-signal”
dbus_g_connection_register_g_object() g_dbus_connection_register_object() - 另见 g_dbus_object_manager_server_export()
dbus_g_connection_unregister_g_object() g_dbus_connection_unregister_object() - 另见 g_dbus_object_manager_server_unexport()
dbus_g_object_type_install_info() 在注册对象时安装反映数据,见 g_dbus_connection_register_object()
dbus_g_proxy_begin_call() g_dbus_proxy_call()
dbus_g_proxy_end_call() g_dbus_proxy_call_finish()
dbus_g_proxy_call() g_dbus_proxy_call_sync()
dbus_g_error_domain_register() g_dbus_error_register_error_domain()
dbus_g_error_has_name() 没有直接等效,请参考g_dbus_error_get_remote_error()
dbus_g_method_return() g_dbus_method_invocation_return_value()
dbus_g_method_return_error() g_dbus_method_invocation_return_error()及其变体
dbus_g_method_get_sender() g_dbus_method_invocation_get_sender()

拥有总线名称

使用dbus-glib,通常您需要手动调用RequestName以拥有一个名称,如下所示片段

error = NULL;
res = dbus_g_proxy_call (system_bus_proxy,
                         "RequestName",
                         &error,
                         G_TYPE_STRING, NAME_TO_CLAIM,
                         G_TYPE_UINT,   DBUS_NAME_FLAG_ALLOW_REPLACEMENT,
                         G_TYPE_INVALID,
                         G_TYPE_UINT,   &result,
                         G_TYPE_INVALID);
if (!res)
  {
    if (error != NULL)
      {
        g_warning ("Failed to acquire %s: %s",
                   NAME_TO_CLAIM, error->message);
        g_error_free (error);
      }
    else
      {
        g_warning ("Failed to acquire %s", NAME_TO_CLAIM);
      }
    goto out;
  }

if (result != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER)
  {
    if (error != NULL)
      {
        g_warning ("Failed to acquire %s: %s",
                   NAME_TO_CLAIM, error->message);
        g_error_free (error);
      }
    else
      {
        g_warning ("Failed to acquire %s", NAME_TO_CLAIM);
      }
    exit (1);
  }

dbus_g_proxy_add_signal (system_bus_proxy, "NameLost",
                         G_TYPE_STRING, G_TYPE_INVALID);
dbus_g_proxy_connect_signal (system_bus_proxy, "NameLost",
                             G_CALLBACK (on_name_lost), NULL, NULL);

/* further setup ... */

虽然您可以使用GDBus通过g_dbus_proxy_call_sync()以这种方式完成某些事情,但仍更倾向于使用此高级API

static void
on_name_acquired (GDBusConnection *connection,
                  const gchar     *name,
                  gpointer         user_data)
{
  /* further setup ... */
}

/* ... */

  owner_id = g_bus_own_name (G_BUS_TYPE_SYSTEM,
                             NAME_TO_CLAIM,
                             G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT,
                             on_bus_acquired,
                             on_name_acquired,
                             on_name_lost,
                             NULL,
                             NULL);

  g_main_loop_run (loop);

  g_bus_unown_name (owner_id);

注意,g_bus_own_name()以异步方式工作,并且需要您进入主循环等待on_name_acquired()回调。另外,请注意,为了避免竞态条件(例如,当您的服务通过方法调用被激活时),您必须在获取名称之前导出您的管理器对象。进行此类准备的最佳回调是on_bus_acquired()

为知名名称创建代理

dbus-glib允许您为知名名称创建代理对象,如下例所示

proxy = dbus_g_proxy_new_for_name (system_bus_connection,
                                   "org.freedesktop.Accounts",
                                   "/org/freedesktop/Accounts",
                                   "org.freedesktop.Accounts");

对于此类构建的DBusGProxy,方法调用将被发送到当前拥有该名称的用户,而且该所有者会随着时间的推移而变化。

这也可以使用GDBusProxy实现

error = NULL;
proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
                                       G_DBUS_PROXY_FLAGS_NONE,
                                       NULL, /* GDBusInterfaceInfo */
                                       "org.freedesktop.Accounts",
                                       "/org/freedesktop/Accounts",
                                       "org.freedesktop.Accounts",
                                       NULL, /* GCancellable */
                                       &error);

为了增加一层安全性,您可以使用GDBusInterfaceInfo类型指定代理预期遵守的D-Bus接口。另外,GDBusProxy加载、缓存并跟踪远程对象上的D-Bus属性的变化。它还设置匹配规则,以便将远程对象的D-Bus信号本地化。

GDBusProxy类型通常不直接使用 - 而是使用由gdbus-codegen生成的子类化GDBusProxy,请参阅“使用gdbus-codegen”部分。

生成代码和文档

使用gdbus-codegen

dbus-glib附带了dbus-binding-tool,它可以生成一个相对不错的D-Bus接口的客户机和服务器端包装器。与GDBus一样,使用gdbus-codegen,并以与其对应的文件作为输入

示例D-Bus Introspection XML

<node>
  <!-- org.gtk.GDBus.Example.ObjectManager.Animal:
       @short_description: Example docs generated by gdbus-codegen
       @since: 2.30

       This D-Bus interface is used to describe a simple animal.
    -->
  <interface name="org.gtk.GDBus.Example.ObjectManager.Animal">
    <!-- Mood: The mood of the animal.
         @since: 2.30

         Known values for this property include
         <literal>Happy</literal> and <literal>Sad</literal>. Use the
         org.gtk.GDBus.Example.ObjectManager.Animal.Poke() method to
         change this property.

         This property influences how often the animal jumps up and
         down, see the
         #org.gtk.GDBus.Example.ObjectManager.Animal::Jumped signal
         for more details.
    -->
    <property name="Mood" type="s" access="read"/>

    <!--
        Poke:
        @make_sad: Whether to make the animal sad.
        @make_happy: Whether to make the animal happy.
        @since: 2.30

        Method used to changing the mood of the animal. See also the
        #org.gtk.GDBus.Example.ObjectManager.Animal:Mood property.
      -->
    <method name="Poke">
      <arg direction="in" type="b" name="make_sad"/>
      <arg direction="in" type="b" name="make_happy"/>
    </method>

    <!--
        Jumped:
        @height: Height, in meters, that the animal jumped.
        @since: 2.30

        Emitted when the animal decides to jump.
      -->
    <signal name="Jumped">
      <arg type="d" name="height"/>
    </signal>

    <!--
        Foo:
        Property with no <quote>since</quote> annotation (should inherit the 2.30 from its containing interface).
      -->
    <property name="Foo" type="s" access="read"/>

    <!--
        Bar:
        @since: 2.36
        Property with a later <quote>since</quote> annotation.
      -->
    <property name="Bar" type="s" access="read"/>
  </interface>

  <!-- org.gtk.GDBus.Example.ObjectManager.Cat:
       @short_description: More example docs generated by gdbus-codegen

       This D-Bus interface is used to describe a cat. Right now there
       are no properties, methods or signals associated with this
       interface so it is essentially a <ulink
       url="http://en.wikipedia.org/wiki/Marker_interface_pattern">Marker
       Interface</ulink>.

       Note that D-Bus objects implementing this interface also
       implement the #org.gtk.GDBus.Example.ObjectManager.Animal
       interface.
    -->
  <interface name="org.gtk.GDBus.Example.ObjectManager.Cat">
  </interface>
</node>

如果这个XML按这种方式处理

gdbus-codegen --interface-prefix org.gtk.GDBus.Example.ObjectManager. \
              --generate-c-code generated-code                        \
              --c-namespace Example                       \
              --c-generate-object-manager                 \
              --generate-docbook generated-docs                       \
              gdbus-example-objectmanager.xml

然后生成两个文件,generated-code.h和generated-code.c。另外,还生成两个带有Docbook XML的XML文件,generated-docs-org.gtk.GDBus.Example.ObjectManager.Animal和generated-docs-org.gtk.GDBus.Example.ObjectManager.Cat。

虽然generated-code.hgenerated-code.c的内容最好通过gdbus-codegen手动页来描述,以下是一个此生成代码的简要使用示例

#include "gdbus-object-manager-example/objectmanager-gen.h"

/* ---------------------------------------------------------------------------------------------------- */

static GDBusObjectManagerServer *manager = NULL;

static gboolean
on_animal_poke (ExampleAnimal          *animal,
                GDBusMethodInvocation  *invocation,
                gboolean                make_sad,
                gboolean                make_happy,
                gpointer                user_data)
{
  if ((make_sad && make_happy) || (!make_sad && !make_happy))
    {
      g_dbus_method_invocation_return_dbus_error (invocation,
                                                  "org.gtk.GDBus.Examples.ObjectManager.Error.Failed",
                                                  "Exactly one of make_sad or make_happy must be TRUE");
      goto out;
    }

  if (make_sad)
    {
      if (g_strcmp0 (example_animal_get_mood (animal), "Sad") == 0)
        {
          g_dbus_method_invocation_return_dbus_error (invocation,
                                                      "org.gtk.GDBus.Examples.ObjectManager.Error.SadAnimalIsSad",
                                                      "Sad animal is already sad");
          goto out;
        }

      example_animal_set_mood (animal, "Sad");
      example_animal_complete_poke (animal, invocation);
      goto out;
    }

  if (make_happy)
    {
      if (g_strcmp0 (example_animal_get_mood (animal), "Happy") == 0)
        {
          g_dbus_method_invocation_return_dbus_error (invocation,
                                                      "org.gtk.GDBus.Examples.ObjectManager.Error.HappyAnimalIsHappy",
                                                      "Happy animal is already happy");
          goto out;
        }

      example_animal_set_mood (animal, "Happy");
      example_animal_complete_poke (animal, invocation);
      goto out;
    }

  g_assert_not_reached ();

 out:
  return G_DBUS_METHOD_INVOCATION_HANDLED;
}


static void
on_bus_acquired (GDBusConnection *connection,
                 const gchar     *name,
                 gpointer         user_data)
{
  ExampleObjectSkeleton *object;
  guint n;

  g_print ("Acquired a message bus connection\n");

  /* Create a new org.freedesktop.DBus.ObjectManager rooted at /example/Animals */
  manager = g_dbus_object_manager_server_new ("/example/Animals");

  for (n = 0; n < 10; n++)
    {
      gchar *s;
      ExampleAnimal *animal;

      /* Create a new D-Bus object at the path /example/Animals/N where N is 000..009 */
      s = g_strdup_printf ("/example/Animals/%03d", n);
      object = example_object_skeleton_new (s);
      g_free (s);

      /* Make the newly created object export the interface
       * org.gtk.GDBus.Example.ObjectManager.Animal (note
       * that @object takes its own reference to @animal).
       */
      animal = example_animal_skeleton_new ();
      example_animal_set_mood (animal, "Happy");
      example_object_skeleton_set_animal (object, animal);
      g_object_unref (animal);

      /* Cats are odd animals - so some of our objects implement the
       * org.gtk.GDBus.Example.ObjectManager.Cat interface in addition
       * to the .Animal interface
       */
      if (n % 2 == 1)
        {
          ExampleCat *cat;
          cat = example_cat_skeleton_new ();
          example_object_skeleton_set_cat (object, cat);
          g_object_unref (cat);
        }

      /* Handle Poke() D-Bus method invocations on the .Animal interface */
      g_signal_connect (animal,
                        "handle-poke",
                        G_CALLBACK (on_animal_poke),
                        NULL); /* user_data */

      /* Export the object (@manager takes its own reference to @object) */
      g_dbus_object_manager_server_export (manager, G_DBUS_OBJECT_SKELETON (object));
      g_object_unref (object);
    }

  /* Export all objects */
  g_dbus_object_manager_server_set_connection (manager, connection);
}

static void
on_name_acquired (GDBusConnection *connection,
                  const gchar     *name,
                  gpointer         user_data)
{
  g_print ("Acquired the name %s\n", name);
}

static void
on_name_lost (GDBusConnection *connection,
              const gchar     *name,
              gpointer         user_data)
{
  g_print ("Lost the name %s\n", name);
}


gint
main (gint argc, gchar *argv[])
{
  GMainLoop *loop;
  guint id;

  loop = g_main_loop_new (NULL, FALSE);

  id = g_bus_own_name (G_BUS_TYPE_SESSION,
                       "org.gtk.GDBus.Examples.ObjectManager",
                       G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT |
                       G_BUS_NAME_OWNER_FLAGS_REPLACE,
                       on_bus_acquired,
                       on_name_acquired,
                       on_name_lost,
                       loop,
                       NULL);

  g_main_loop_run (loop);

  g_bus_unown_name (id);
  g_main_loop_unref (loop);

  return 0;
}

另一方面,这是一个使用生成代码的客户端应用程序

#include "gdbus-object-manager-example/objectmanager-gen.h"

/* ---------------------------------------------------------------------------------------------------- */

static void
print_objects (GDBusObjectManager *manager)
{
  GList *objects;
  GList *l;

  g_print ("Object manager at %s\n", g_dbus_object_manager_get_object_path (manager));
  objects = g_dbus_object_manager_get_objects (manager);
  for (l = objects; l != NULL; l = l->next)
    {
      ExampleObject *object = EXAMPLE_OBJECT (l->data);
      GList *interfaces;
      GList *ll;
      g_print (" - Object at %s\n", g_dbus_object_get_object_path (G_DBUS_OBJECT (object)));

      interfaces = g_dbus_object_get_interfaces (G_DBUS_OBJECT (object));
      for (ll = interfaces; ll != NULL; ll = ll->next)
        {
          GDBusInterface *interface = G_DBUS_INTERFACE (ll->data);
          g_print ("   - Interface %s\n", g_dbus_interface_get_info (interface)->name);

          /* Note that @interface is really a GDBusProxy instance - and additionally also
           * an ExampleAnimal or ExampleCat instance - either of these can be used to
           * invoke methods on the remote object. For example, the generated function
           *
           *  void example_animal_call_poke_sync (ExampleAnimal  *proxy,
           *                                      gboolean        make_sad,
           *                                      gboolean        make_happy,
           *                                      GCancellable   *cancellable,
           *                                      GError        **error);
           *
           * can be used to call the Poke() D-Bus method on the .Animal interface.
           * Additionally, the generated function
           *
           *  const gchar *example_animal_get_mood (ExampleAnimal *object);
           *
           * can be used to get the value of the :Mood property.
           */
        }
      g_list_free_full (interfaces, g_object_unref);
    }
  g_list_free_full (objects, g_object_unref);
}

static void
on_object_added (GDBusObjectManager *manager,
                 GDBusObject        *object,
                 gpointer            user_data)
{
  gchar *owner;
  owner = g_dbus_object_manager_client_get_name_owner (G_DBUS_OBJECT_MANAGER_CLIENT (manager));
  g_print ("Added object at %s (owner %s)\n", g_dbus_object_get_object_path (object), owner);
  g_free (owner);
}

static void
on_object_removed (GDBusObjectManager *manager,
                   GDBusObject        *object,
                   gpointer            user_data)
{
  gchar *owner;
  owner = g_dbus_object_manager_client_get_name_owner (G_DBUS_OBJECT_MANAGER_CLIENT (manager));
  g_print ("Removed object at %s (owner %s)\n", g_dbus_object_get_object_path (object), owner);
  g_free (owner);
}

static void
on_notify_name_owner (GObject    *object,
                      GParamSpec *pspec,
                      gpointer    user_data)
{
  GDBusObjectManagerClient *manager = G_DBUS_OBJECT_MANAGER_CLIENT (object);
  gchar *name_owner;

  name_owner = g_dbus_object_manager_client_get_name_owner (manager);
  g_print ("name-owner: %s\n", name_owner);
  g_free (name_owner);
}

static void
on_interface_proxy_properties_changed (GDBusObjectManagerClient *manager,
                                       GDBusObjectProxy         *object_proxy,
                                       GDBusProxy               *interface_proxy,
                                       GVariant                 *changed_properties,
                                       const gchar *const       *invalidated_properties,
                                       gpointer                  user_data)
{
  GVariantIter iter;
  const gchar *key;
  GVariant *value;
  gchar *s;

  g_print ("Properties Changed on %s:\n", g_dbus_object_get_object_path (G_DBUS_OBJECT (object_proxy)));
  g_variant_iter_init (&iter, changed_properties);
  while (g_variant_iter_next (&iter, "{&sv}", &key, &value))
    {
      s = g_variant_print (value, TRUE);
      g_print ("  %s -> %s\n", key, s);
      g_variant_unref (value);
      g_free (s);
    }
}

gint
main (gint argc, gchar *argv[])
{
  GDBusObjectManager *manager;
  GMainLoop *loop;
  GError *error;
  gchar *name_owner;

  manager = NULL;
  loop = NULL;

  loop = g_main_loop_new (NULL, FALSE);

  error = NULL;
  manager = example_object_manager_client_new_for_bus_sync (G_BUS_TYPE_SESSION,
                                                            G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE,
                                                            "org.gtk.GDBus.Examples.ObjectManager",
                                                            "/example/Animals",
                                                            NULL, /* GCancellable */
                                                            &error);
  if (manager == NULL)
    {
      g_printerr ("Error getting object manager client: %s", error->message);
      g_error_free (error);
      goto out;
    }

  name_owner = g_dbus_object_manager_client_get_name_owner (G_DBUS_OBJECT_MANAGER_CLIENT (manager));
  g_print ("name-owner: %s\n", name_owner);
  g_free (name_owner);

  print_objects (manager);

  g_signal_connect (manager,
                    "notify::name-owner",
                    G_CALLBACK (on_notify_name_owner),
                    NULL);
  g_signal_connect (manager,
                    "object-added",
                    G_CALLBACK (on_object_added),
                    NULL);
  g_signal_connect (manager,
                    "object-removed",
                    G_CALLBACK (on_object_removed),
                    NULL);
  g_signal_connect (manager,
                    "interface-proxy-properties-changed",
                    G_CALLBACK (on_interface_proxy_properties_changed),
                    NULL);

  g_main_loop_run (loop);

 out:
  if (manager != NULL)
    g_object_unref (manager);
  if (loop != NULL)
    g_main_loop_unref (loop);

  return 0;
}