迁移到 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 XML 的 API,而 dbus-glib 则不提供。
- GTestDBus 提供了创建隔离单元测试的 API
API 对比
dbus-glib | GDBus |
---|---|
DBusGConnection |
GDBusConnection |
DBusGProxy |
GDBusProxy ,GDBusInterface - 另见 GDBusObjectProxy |
DBusGObject |
GDBusInterfaceSkeleton ,GDBusInterface - 另见 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>
如果这个按这种方式处理
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文件,generated-docs-org.gtk.GDBus.Example.ObjectManager.Animal和generated-docs-org.gtk.GDBus.Example.ObjectManager.Cat。
虽然generated-code.h
和generated-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;
}