强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

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-bussystemd 内置轻量级,适合 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 类型构造解析
yguint8g_variant_new_byte(x)g_variant_get_byte(v)
bgbooleang_variant_new_boolean(x)g_variant_get_boolean(v)
ngint16g_variant_new_int16(x)g_variant_get_int16(v)
igint32g_variant_new_int32(x)g_variant_get_int32(v)
xgint64g_variant_new_int64(x)g_variant_get_int64(v)
sconst gchar*g_variant_new_string(s)g_variant_get_string(v, NULL)
oconst gchar*g_variant_new_object_path(s)g_variant_get_string(v, NULL)
dgdoubleg_variant_new_double(x)g_variant_get_double(v)
asGVariantIter*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 绑定代码
GVariantD-Bus 类型系统的 GLib 表示
GDBusObjectManager管理多个 D-Bus 对象

扩展阅读