【Linux IPC の d-bus】gdbus 常用数据结构与库函数
常用数据结构
在 GDBus(基于 D-Bus 的通信框架)中,最常用的数据类型主要是通过 GVariant 来表示的。GVariant 是 GLib 提供的一种用于存储任意复杂数据结构的容器类型,它支持多种基础数据类型以及嵌套结构。以下是 GDBus 中最常用的一些数据类型及其对应的 D-Bus 类型代码:
基础数据类型
-
BYTE (
y)- GLib 数据类型:
guchar - 描述: 无符号8位整数 (0-255)
- GLib 数据类型:
-
BOOLEAN (
b)- GLib 数据类型:
gboolean - 描述: 布尔值,通常为
TRUE或FALSE
- GLib 数据类型:
-
INT16 (
n)- GLib 数据类型:
gint16 - 描述: 有符号16位整数 (-32768到32767)
- GLib 数据类型:
-
UINT16 (
q)- GLib 数据类型:
guint16 - 描述: 无符号16位整数 (0到65535)
- GLib 数据类型:
-
INT32 (
i)- GLib 数据类型:
gint32 - 描述: 有符号32位整数 (-2^31 到 2^31 - 1)
- GLib 数据类型:
-
UINT32 (
u)- GLib 数据类型:
guint32 - 描述: 无符号32位整数 (0到4294967295)
- GLib 数据类型:
-
INT64 (
x)- GLib 数据类型:
gint64 - 描述: 有符号64位整数
- GLib 数据类型:
-
UINT64 (
t)- GLib 数据类型:
guint64 - 描述: 无符号64位整数
- GLib 数据类型:
-
DOUBLE (
d)- GLib 数据类型:
gdouble - 描述: 双精度浮点数
- GLib 数据类型:
-
STRING (
s)- GLib 数据类型:
gchar* - 描述: UTF-8 编码的字符串
- GLib 数据类型:
复杂数据类型
-
ARRAY (
a)- 示例:
ai表示一个整数数组 - 描述: 包含相同类型的元素集合
- 示例:
-
VARIANT (
v)- 描述: 可以包含任何其他类型的变体值
-
DICTIONARY (
a{sv}或a{ss}等)- 示例:
a{ss}表示键和值都是字符串的字典 - 描述: 键值对的集合,其中键通常是字符串,值可以是任意类型
- 示例:
-
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: 代理对象。num1,num2: 输入参数。&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)。
让我们来详细分解一下:
核心概念澄清
-
客户端 vs. 服务端:
-
服务端:提供 D-Bus 服务的一方,它创建了 D-Bus Object(在某个 Object Path 上,例如
/org/example/MyObject),并实现了该 Object 的接口(Interface,例如org.example.MyInterface),包含方法(Methods)、信号(Signals)和属性(Properties)。 -
客户端:希望使用该服务的一方。它连接到总线,寻找服务,并调用其方法或监听其信号。
-
-
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):
// 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):
# 创建 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 不是“客户端”本身,而是客户端为了高效、便捷地与远程服务交互而创建的一个强大的“本地助手”或“代言人”。

浙公网安备 33010602011771号