08 - GDBus(C/GLib 编程)
第 08 章:GDBus(C/GLib 编程)
8.1 GDBus 概述
GDBus 是 GLib/GIO 库提供的 D-Bus 绑定,是 GNOME 生态中最常用的 C 语言 D-Bus 库。它替代了旧的 libdbus-glib,提供了更现代的 API。
| 库 | 状态 | 说明 |
|---|
libdbus-1 | 低层库 | D-Bus 参考实现,API 复杂 |
libdbus-glib | 已弃用 | GLib 绑定的旧版本 |
GDBus (GIO) | 推荐 | 现代 GLib 绑定,功能完整 |
sd-bus | systemd 内置 | 轻量级,适合 systemd 集成 |
核心组件
| 组件 | 用途 | 类比 |
|---|
GDBusProxy | 客户端代理对象 | 自动调用方法、缓存属性 |
GDBusInterfaceSkeleton | 服务端骨架对象 | 接收方法调用、发出信号 |
GDBusConnection | 底层连接管理 | 类似 Socket |
gdbus-codegen | 代码生成工具 | 从 XML 生成 C 绑定 |
GDBusObjectManager | 多对象管理 | 管理一组对象 |
8.2 GDBusProxy — 客户端代理
8.2.1 创建代理
#include <gio/gio.h>
int main(void) {
GError *error = NULL;
/* 创建代理 */
GDBusProxy *proxy = g_dbus_proxy_new_for_bus_sync(
G_BUS_TYPE_SESSION, /* 总线类型 */
G_DBUS_PROXY_FLAGS_NONE, /* 标志 */
NULL, /* introspection data */
"org.freedesktop.DBus", /* bus name */
"/org/freedesktop/DBus", /* object path */
"org.freedesktop.DBus", /* interface */
NULL, /* cancellable */
&error
);
if (error) {
g_printerr("错误: %s\n", error->message);
g_error_free(error);
return 1;
}
/* 调用方法 */
GVariant *result = g_dbus_proxy_call_sync(
proxy,
"ListNames",
NULL, /* parameters */
G_DBUS_CALL_FLAGS_NONE,
5000, /* timeout ms */
NULL, /* cancellable */
&error
);
if (result) {
GVariantIter *iter;
const char *name;
g_variant_get(result, "(as)", &iter);
g_print("总线名称:\n");
while (g_variant_iter_next(iter, "&s", &name)) {
g_print(" %s\n", name);
}
g_variant_iter_free(iter);
g_variant_unref(result);
}
g_object_unref(proxy);
return 0;
}
8.2.2 代理标志
| 标志 | 说明 |
|---|
G_DBUS_PROXY_FLAGS_NONE | 默认行为 |
G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | 不预加载属性 |
G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS | 不连接信号 |
G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START | 不自动激活服务 |
8.2.3 读取属性
/* 读取属性 */
GVariant *value = g_dbus_proxy_get_cached_property(proxy, "Version");
if (value) {
const char *version;
g_variant_get(value, "&s", &version);
g_print("D-Bus 版本: %s\n", version);
g_variant_unref(value);
}
/* 获取所有属性名 */
gchar **props = g_dbus_proxy_get_cached_property_names(proxy);
for (int i = 0; props[i]; i++) {
g_print("属性: %s\n", props[i]);
}
g_strfreev(props);
8.2.4 连接信号
static void on_signal(GDBusProxy *proxy,
gchar *sender,
gchar *signal_name,
GVariant *parameters,
gpointer user_data) {
g_print("收到信号: %s from %s\n", signal_name, sender);
gchar *params = g_variant_print(parameters, TRUE);
g_print("参数: %s\n", params);
g_free(params);
}
/* 连接信号 */
g_signal_connect(proxy, "g-signal", G_CALLBACK(on_signal), NULL);
8.3 GDBusInterfaceSkeleton — 服务端骨架
8.3.1 基本服务实现
#include <gio/gio.h>
/* 1. 定义接口(通常由 gdbus-codegen 生成,这里手动演示) */
static GDBusInterfaceInfo *my_iface_info = NULL;
/* 2. 处理方法调用 */
static GVariant *handle_get_property(GDBusConnection *conn,
const gchar *sender,
const gchar *object_path,
const gchar *interface,
const gchar *property,
GError **error,
gpointer user_data) {
if (g_strcmp0(property, "Name") == 0) {
return g_variant_new_string("MyService");
}
return NULL;
}
/* 3. 方法调用处理函数 */
static void handle_method_call(GDBusConnection *conn,
const gchar *sender,
const gchar *object_path,
const gchar *interface,
const gchar *method,
GVariant *parameters,
GDBusMethodInvocation *invocation,
gpointer user_data) {
if (g_strcmp0(method, "Ping") == 0) {
g_dbus_method_invocation_return_value(invocation,
g_variant_new("(s)", "Pong"));
} else if (g_strcmp0(method, "Echo") == 0) {
const gchar *msg;
g_variant_get(parameters, "(&s)", &msg);
g_dbus_method_invocation_return_value(invocation,
g_variant_new("(s)", msg));
} else {
g_dbus_method_invocation_return_dbus_error(invocation,
"org.freedesktop.DBus.Error.UnknownMethod",
"Unknown method");
}
}
8.4 gdbus-codegen — 代码生成
8.4.1 定义接口 XML
<?xml version="1.0"?>
<node xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd">
<interface name="org.example.Calculator">
<doc:doc>
<doc:description>
<doc:para>简单的计算器服务</doc:para>
</doc:description>
</doc:doc>
<method name="Add">
<doc:doc>
<doc:summary>两数相加</doc:summary>
</doc:doc>
<arg name="a" type="d" direction="in"/>
<arg name="b" type="d" direction="in"/>
<arg name="result" type="d" direction="out"/>
</method>
<method name="Multiply">
<arg name="a" type="d" direction="in"/>
<arg name="b" type="d" direction="in"/>
<arg name="result" type="d" direction="out"/>
</method>
<signal name="CalculationPerformed">
<arg name="operation" type="s"/>
<arg name="result" type="d"/>
</signal>
<property name="History" type="as" access="read"/>
<property name="Precision" type="i" access="readwrite"/>
</interface>
</node>
8.4.2 生成代码
# 生成客户端和服务端代码
gdbus-codegen \
--generate-c-code calculator \
--c-namespace Calc \
--interface-prefix org.example \
--c-generate-object-manager \
org.example.Calculator.xml
生成的文件:
calculator.h — 头文件calculator.c — 实现文件CalcCalculator — 客户端代理类CalcCalculatorSkeleton — 服务端骨架类
8.4.3 使用生成的代码 — 服务端
#include <gio/gio.h>
#include "calculator.h"
/* 属性值 */
static gchar **history = NULL;
static gint precision = 2;
/* 处理 Add 方法 */
static gboolean on_handle_add(CalcCalculator *object,
GDBusMethodInvocation *invocation,
gdouble a, gdouble b) {
gdouble result = a + b;
/* 更新历史 */
gchar *entry = g_strdup_printf("%.2f + %.2f = %.2f", a, b, result);
/* ... 添加到 history ... */
g_free(entry);
/* 发送信号 */
calc_calculator_emit_calculation_performed(object, "add", result);
/* 返回结果 */
calc_calculator_complete_add(object, invocation, result);
return TRUE;
}
int main(void) {
GMainLoop *loop = g_main_loop_new(NULL, FALSE);
GError *error = NULL;
GDBusConnection *conn = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, &error);
if (!conn) {
g_printerr("连接失败: %s\n", error->message);
return 1;
}
/* 创建骨架 */
CalcCalculator *skeleton = calc_calculator_skeleton_new();
/* 连接方法处理 */
g_signal_connect(skeleton, "handle-add", G_CALLBACK(on_handle_add), NULL);
/* 注册到总线 */
g_dbus_interface_skeleton_export(
G_DBUS_INTERFACE_SKELETON(skeleton),
conn,
"/org/example/Calculator",
&error
);
/* 请求总线名称 */
g_bus_own_name_on_connection(conn,
"org.example.Calculator",
G_BUS_NAME_OWNER_FLAGS_NONE,
NULL, NULL, NULL, NULL);
g_print("计算器服务已启动\n");
g_main_loop_run(loop);
/* 清理 */
g_dbus_interface_skeleton_unexport(G_DBUS_INTERFACE_SKELETON(skeleton));
g_object_unref(skeleton);
g_object_unref(conn);
g_main_loop_unref(loop);
return 0;
}
编译:
gcc -o calculator-service calculator-service.c calculator.c \
$(pkg-config --cflags --libs gio-2.0 glib-2.0)
8.4.4 使用生成的代码 — 客户端
#include <gio/gio.h>
#include "calculator.h"
int main(void) {
GError *error = NULL;
GMainLoop *loop = g_main_loop_new(NULL, FALSE);
GDBusConnection *conn = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, &error);
/* 创建代理 */
CalcCalculator *proxy = calc_calculator_proxy_new_sync(
conn,
G_DBUS_PROXY_FLAGS_NONE,
"org.example.Calculator",
"/org/example/Calculator",
NULL,
&error
);
if (!proxy) {
g_printerr("创建代理失败: %s\n", error->message);
return 1;
}
/* 调用 Add */
gdouble result = 0;
calc_calculator_call_add_sync(proxy, 3.14, 2.72, &result, NULL, &error);
g_print("3.14 + 2.72 = %.2f\n", result);
/* 调用 Multiply */
calc_calculator_call_multiply_sync(proxy, 5.0, 6.0, &result, NULL, &error);
g_print("5.0 * 6.0 = %.2f\n", result);
/* 读取属性 */
const gchar *const *hist = calc_calculator_get_history(proxy);
if (hist) {
g_print("历史记录:\n");
for (int i = 0; hist[i]; i++) {
g_print(" %s\n", hist[i]);
}
}
/* 监听信号 */
g_signal_connect(proxy, "calculation-performed",
G_CALLBACK(on_calculation), NULL);
g_object_unref(proxy);
g_object_unref(conn);
return 0;
}
static void on_calculation(CalcCalculator *proxy,
const gchar *operation,
gdouble result,
gpointer user_data) {
g_print("计算完成: %s = %.2f\n", operation, result);
}
8.5 GDBusObjectManager
管理多个 D-Bus 对象的便捷接口:
#include <gio/gio.h>
/* 使用 ObjectManager 管理多个对象 */
GDBusObjectManagerServer *manager = g_dbus_object_manager_server_new("/org/example");
/* 创建对象 */
GDBusObjectSkeleton *obj = g_dbus_object_skeleton_new("/org/example/Devices/0");
/* 添加接口骨架 */
GDBusInterfaceSkeleton *iface = G_DBUS_INTERFACE_SKELETON(my_device_skeleton_new());
g_dbus_object_skeleton_add_interface(obj, iface);
/* 注册到管理器 */
g_dbus_object_manager_server_export(manager, G_DBUS_OBJECT(obj));
/* 连接到总线 */
g_dbus_object_manager_server_set_connection(manager, conn);
客户端可以使用 GetManagedObjects 一次性获取所有对象:
GDBusObjectManagerClient *client = g_dbus_object_manager_client_new_for_bus_sync(
G_BUS_TYPE_SESSION,
G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE,
"org.example",
"/org/example",
NULL, NULL, NULL, NULL, &error
);
GList *objects = g_dbus_object_manager_get_objects(G_DBUS_OBJECT_MANAGER(client));
for (GList *l = objects; l; l = l->next) {
GDBusObject *obj = G_DBUS_OBJECT(l->data);
g_print("对象: %s\n", g_dbus_object_get_object_path(obj));
GList *ifaces = g_dbus_object_get_interfaces(obj);
for (GList *i = ifaces; i; i = i->next) {
GDBusInterface *iface = G_DBUS_INTERFACE(i->data);
g_print(" 接口: %s\n", g_dbus_interface_get_name(iface));
}
}
g_list_free_full(objects, g_object_unref);
8.6 类型映射
D-Bus → GVariant 类型对照
| D-Bus 签名 | GVariant 类型 | 构造 | 解析 |
|---|
y | guint8 | g_variant_new_byte(x) | g_variant_get_byte(v) |
b | gboolean | g_variant_new_boolean(x) | g_variant_get_boolean(v) |
n | gint16 | g_variant_new_int16(x) | g_variant_get_int16(v) |
i | gint32 | g_variant_new_int32(x) | g_variant_get_int32(v) |
x | gint64 | g_variant_new_int64(x) | g_variant_get_int64(v) |
s | const gchar* | g_variant_new_string(s) | g_variant_get_string(v, NULL) |
o | const gchar* | g_variant_new_object_path(s) | g_variant_get_string(v, NULL) |
d | gdouble | g_variant_new_double(x) | g_variant_get_double(v) |
as | GVariantIter* | g_variant_new_strv(strv, len) | g_variant_get(v, "^as", &strv) |
a{sv} | dict | 见下例 | 见下例 |
构造 a{sv} 字典
/* 创建 a{sv} 字典 */
GVariantBuilder builder;
g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}"));
g_variant_builder_add(&builder, "{sv}", "name",
g_variant_new_string("test"));
g_variant_builder_add(&builder, "{sv}", "count",
g_variant_new_int32(42));
g_variant_builder_add(&builder, "{sv}", "pi",
g_variant_new_double(3.14));
GVariant *dict = g_variant_builder_end(&builder);
/* 解析 a{sv} 字典 */
GVariantIter iter;
const gchar *key;
GVariant *value;
g_variant_iter_init(&iter, dict);
while (g_variant_iter_next(&iter, "{&sv}", &key, &value)) {
g_print(" %s = %s\n", key, g_variant_print(value, FALSE));
g_variant_unref(value);
}
构造复杂结构
/* (si) 结构体 */
GVariant *struct_val = g_variant_new("(si)", "hello", 42);
/* a(si) 结构体数组 */
GVariantBuilder arr;
g_variant_builder_init(&arr, G_VARIANT_TYPE("a(si)"));
g_variant_builder_add(&arr, "(si)", "first", 1);
g_variant_builder_add(&arr, "(si)", "second", 2);
GVariant *array = g_variant_builder_end(&arr);
8.7 编译与调试
8.7.1 编译 GDBus 应用
# 基本编译
gcc -o my-app my-app.c $(pkg-config --cflags --libs gio-2.0 glib-2.0)
# 使用 gdbus-codegen 生成代码后
gdbus-codegen --generate-c-code my-bindings --c-namespace My \
org.example.MyService.xml
gcc -o my-service service.c my-bindings.c $(pkg-config --cflags --libs gio-2.0)
8.7.2 GLib 调试环境变量
# 启用 D-Bus 调试消息
G_DBUS_DEBUG=all ./my-app
# 只看消息
G_DBUS_DEBUG=message ./my-app
# 只看信号
G_DBUS_DEBUG=signal ./my-app
# 只看属性
G_DBUS_DEBUG=properties ./my-app
8.7.3 常见错误
| 错误 | 原因 | 解决方案 |
|---|
GDBus.Error:... | 远程错误 | 检查错误名称和消息 |
| 连接失败 | 总线不可用 | 检查 DBUS_SESSION_BUS_ADDRESS |
| 名称已被占用 | 服务重复 | 检查是否有其他实例 |
| 类型不匹配 | 签名错误 | 检查 g_variant_new 格式字符串 |
本章小结
| 概念 | 说明 |
|---|
| GDBusProxy | 客户端代理,自动缓存属性、路由信号 |
| GDBusInterfaceSkeleton | 服务端骨架,处理方法调用 |
| gdbus-codegen | 从 XML 生成 C 绑定代码 |
| GVariant | D-Bus 类型系统的 GLib 表示 |
| GDBusObjectManager | 管理多个 D-Bus 对象 |
扩展阅读