【Linux IPC の d-bus】gdbus 常用数据结构与库函数

常用数据结构

在 GDBus(基于 D-Bus 的通信框架)中,最常用的数据类型主要是通过 GVariant 来表示的。GVariant 是 GLib 提供的一种用于存储任意复杂数据结构的容器类型,它支持多种基础数据类型以及嵌套结构。以下是 GDBus 中最常用的一些数据类型及其对应的 D-Bus 类型代码:

基础数据类型

  1. BYTE (y)

    • GLib 数据类型guchar
    • 描述: 无符号8位整数 (0-255)
  2. BOOLEAN (b)

    • GLib 数据类型gboolean
    • 描述: 布尔值,通常为 TRUE 或 FALSE
  3. INT16 (n)

    • GLib 数据类型gint16
    • 描述: 有符号16位整数 (-32768到32767)
  4. UINT16 (q)

    • GLib 数据类型guint16
    • 描述: 无符号16位整数 (0到65535)
  5. INT32 (i)

    • GLib 数据类型gint32
    • 描述: 有符号32位整数 (-2^31 到 2^31 - 1)
  6. UINT32 (u)

    • GLib 数据类型guint32
    • 描述: 无符号32位整数 (0到4294967295)
  7. INT64 (x)

    • GLib 数据类型gint64
    • 描述: 有符号64位整数
  8. UINT64 (t)

    • GLib 数据类型guint64
    • 描述: 无符号64位整数
  9. DOUBLE (d)

    • GLib 数据类型gdouble
    • 描述: 双精度浮点数
  10. STRING (s)

    • GLib 数据类型gchar*
    • 描述: UTF-8 编码的字符串

复杂数据类型

  1. ARRAY (a)

    • 示例ai 表示一个整数数组
    • 描述: 包含相同类型的元素集合
  2. VARIANT (v)

    • 描述: 可以包含任何其他类型的变体值
  3. DICTIONARY (a{sv} 或 a{ss} 等)

    • 示例a{ss} 表示键和值都是字符串的字典
    • 描述: 键值对的集合,其中键通常是字符串,值可以是任意类型
  4. STRUCTURE (r 或括号形式如 (ii) )

    • 示例(is) 表示一个整数和一个字符串组成的结构
    • 描述: 固定顺序的多个值的组合

示例

创建一个简单的 GVariant
// 创建一个 INT32 类型的 GVariant
GVariant *variant = g_variant_new_int32(42);

// 创建一个 STRING 类型的 GVariant
GVariant *string_variant = g_variant_new_string("Hello, World!");

// 创建一个 ARRAY 类型的 GVariant
GVariantBuilder builder;
g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY);
g_variant_builder_add(&builder, "i", 1);
g_variant_builder_add(&builder, "i", 2);
g_variant_builder_add(&builder, "i", 3);
GVariant *array_variant = g_variant_builder_end(&builder);
使用 GVariant 在 D-Bus 方法调用中传递参数

假设你有一个 D-Bus 接口方法 AddNumbers 需要接收两个整数并返回它们的和:

<method name="AddNumbers">
  <arg type="i" name="num1" direction="in"/>
  <arg type="i" name="num2" direction="in"/>
  <arg type="i" name="sum" direction="out"/>
</method>

你可以这样实现客户端调用:

gint num1 = 5, num2 = 7;
gint sum;
com_example_calculator_call_add_numbers_sync(proxy, num1, num2, &sum, NULL, &error);
if (!error) {
    g_print("Sum: %d\n", sum);
}

而在服务端处理该方法时,可以通过 GVariant 获取输入参数,并设置输出参数:

static void handle_add_numbers(ComExampleCalculator *object, GDBusMethodInvocation *invocation, gint num1, gint num2) {
    gint sum = num1 + num2;
    com_example_calculator_complete_add_numbers(object, invocation, sum);
}

常用库函数

在使用 GDBus 进行开发时,有几个关键的库函数和工具是经常使用的。这些函数主要用于创建和管理 D-Bus 连接、注册对象、调用远程方法、处理信号等。以下是一些最常用的 GDBus 库函数及其简要说明:

1. 建立连接

  • g_bus_get_sync(): 同步获取一个 D-Bus 连接。
    GDBusConnection *connection = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, &error);
    • G_BUS_TYPE_SESSION: 表示会话总线。
    • NULL: 取消操作的上下文(Cancellable)。
    • &error: 错误信息。

2. 注册对象到 D-Bus 上

  • g_dbus_interface_skeleton_export(): 将接口骨架导出到 D-Bus 上。
    gboolean success = g_dbus_interface_skeleton_export(G_DBUS_INTERFACE_SKELETON(service), connection, "/com/example/calculator", &error);
    • G_DBUS_INTERFACE_SKELETON(service): 要导出的服务实例。
    • connection: D-Bus 连接。
    • "/com/example/calculator": 对象路径。
    • &error: 错误信息。

3. 创建代理对象

  • com_example_calculator_proxy_new_sync(): 创建一个新的代理对象,用于与远程服务进行通信。
    ComExampleCalculator *proxy = com_example_calculator_proxy_new_sync(connection, G_DBUS_PROXY_FLAGS_NONE, "com.example.Calculator", "/com/example/calculator", NULL, &error);

4. 调用远程方法

  • com_example_calculator_call_add_numbers_sync(): 同步调用远程方法。
    gboolean success = com_example_calculator_call_add_numbers_sync(proxy, num1, num2, &result, NULL, &error);
    • proxy: 代理对象。
    • num1num2: 输入参数。
    • &result: 输出结果。
    • NULL: 取消操作的上下文(Cancellable)。
    • &error: 错误信息。

5. 发送信号

  • com_example_calculator_emit_status_update(): 发送信号通知客户端。
    com_example_calculator_emit_status_update(object, "Calculation completed.");

6. 处理信号

  • g_signal_connect(): 连接信号处理函数。
    g_signal_connect(proxy, "status-update", G_CALLBACK(on_status_update), NULL);
    • proxy: 代理对象。
    • "status-update": 信号名称。
    • G_CALLBACK(on_status_update): 回调函数。
    • NULL: 用户数据。

7. 使用 GVariant 构建复杂数据结构

  • g_variant_builder_new(): 创建一个新的变体构建器。

    GVariantBuilder *builder = g_variant_builder_new(G_VARIANT_TYPE("av"));
    • G_VARIANT_TYPE("av"): 指定构建器类型为数组(a)中的变体(v)。
  • g_variant_builder_add(): 添加元素到构建器中。

    g_variant_builder_add(builder, "{ss}", "location", "file:///tmp/00000001/1.mp3");
  • g_variant_builder_end(): 完成构建并生成 GVariant 对象。

    GVariant *playlist = g_variant_builder_end(builder);

8. 序列化与反序列化

  • g_variant_new_from_data(): 根据二进制数据创建 GVariant 对象。

    GVariant *playlist = g_variant_new_from_data(G_VARIANT_TYPE("av"), data, size, TRUE, NULL, NULL);
    • G_VARIANT_TYPE("av"): 数据类型。
    • data: 二进制数据。
    • size: 数据大小。
    • TRUE: 是否信任数据来源。
  • g_variant_iter_new(): 创建一个新的迭代器用于遍历 GVariant 数组。

    GVariantIter *iter = g_variant_iter_new(playlist);
  • g_variant_iter_next(): 遍历 GVariant 数组中的下一个元素。

    while (g_variant_iter_next(iter, "v", &item)) {
        // 处理每个 item
    }

 

扩展:dbus中的proxy (代理)就是客户端的意思吗?

D-Bus Proxy 是客户端为了与某个服务(Service)/Object 进行方便、类型安全的交互而创建的一个本地代表(Representative)或句柄(Handle)。

让我们来详细分解一下:

核心概念澄清

  1. 客户端 vs. 服务端

    • 服务端:提供 D-Bus 服务的一方,它创建了 D-Bus Object(在某个 Object Path 上,例如 /org/example/MyObject),并实现了该 Object 的接口(Interface,例如 org.example.MyInterface),包含方法(Methods)、信号(Signals)和属性(Properties)。

    • 客户端:希望使用该服务的一方。它连接到总线,寻找服务,并调用其方法或监听其信号。

  2. Proxy 的角色

    • Proxy 是客户端程序中的一个对象

    • 它的作用是代表一个远程的 D-Bus Object。当你操作这个本地的 Proxy 对象时(比如调用它的一个方法),它会自动帮你完成底层的 D-Bus 消息编排、发送、接收和解析等一系列复杂工作。

一个生动的比喻

把 D-Bus 通信比作电话系统:

  • 服务:一家公司(例如 org.example.CalculatorService)。

  • Object Path:公司里的一个分机号(例如 /org/example/Calculator)。

  • Interface:这个分机提供的服务类型(例如 org.example.CalculatorInterface,提供加减乘除服务)。

  • Proxy你手机通讯录里存储的“某某公司客服”这个联系人

    • 这个联系人(Proxy)本身不是公司,但它代表了你与公司沟通的渠道。

    • 你想查询信息时,不需要手动拨号、转分机、说明你的需求,你只需要点击这个联系人(调用 Proxy 的方法),手机(D-Bus 库)就会自动帮你完成所有底层通信。

    • 这个联系人还可以提醒你公司有新公告了(相当于接收信号)。

为什么需要 Proxy?(Proxy 的好处)

如果没有 Proxy,客户端需要直接处理原始的 D-Bus 消息,代码会非常繁琐和容易出错。

没有 Proxy 的代码(伪代码,使用低层 API):

c
// 1. 手动创建方法调用消息
DBusMessage* message = dbus_message_new_method_call(
    "org.example.CalculatorService", // 服务名
    "/org/example/Calculator",       // 对象路径
    "org.example.CalculatorInterface", // 接口名
    "Add"                            // 方法名
);

// 2. 手动打包参数
dbus_int32_t x = 5, y = 3;
dbus_message_append_args(message, DBUS_TYPE_INT32, &x, DBUS_TYPE_INT32, &y, DBUS_TYPE_INVALID);

// 3. 发送消息并等待回复
DBusPendingCall* pending;
dbus_connection_send_with_reply(connection, message, &pending, -1);
dbus_connection_flush(connection);
dbus_message_unref(message);

// 4. 阻塞等待回复
dbus_pending_call_block(pending);
DBusMessage* reply = dbus_pending_call_steal_reply(pending);

// 5. 手动解析回复
dbus_int32_t result;
if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_METHOD_RETURN) {
    dbus_message_get_args(reply, NULL, DBUS_TYPE_INT32, &result, DBUS_TYPE_INVALID);
    printf("Result: %d\n", result);
} else {
    // 处理错误
}

// 6. 清理资源
dbus_message_unref(reply);
dbus_pending_call_unref(pending);

使用 Proxy 的代码(例如,使用 GDBus 或 Qt D-Bus):

python
# 创建 Proxy (假设使用 GDBus 风格的 Python 绑定)
calculator = Gio.DBusProxy.new_sync(
    bus, flags, info,
    "org.example.CalculatorService",
    "/org/example/Calculator",
    "org.example.CalculatorInterface"
)

# 直接像调用本地方法一样使用!
result = calculator.call_sync('Add', GLib.Variant('(ii)', (5, 3)), 0, -1, None)
print(f"Result: {result[0]}")

可以看到,Proxy 将复杂的 D-Bus 协议细节隐藏了起来,让客户端代码变得清晰、直观,并且是类型安全的(在编译或运行时可以检查参数和返回值类型)。

总结

 
 
概念 角色 类比
D-Bus 服务 提供功能的远程程序 公司
D-Bus Object 服务提供的具体资源 公司的某个部门/分机
客户端 使用服务的程序 打电话的客户
Proxy 客户端本地代表远程 Object 的便捷对象 手机通讯录里的联系人

所以,下次当你看到 DBusProxy(GLib/GIO)、QDBusInterface(Qt)或 dbus.Interface(Python dbus)时,请记住:

Proxy 不是“客户端”本身,而是客户端为了高效、便捷地与远程服务交互而创建的一个强大的“本地助手”或“代言人”。

posted @ 2025-11-14 01:07  FBshark  阅读(29)  评论(0)    收藏  举报