DBusMessage 消息参数处理
1. 概述
消息参数存储在消息体(body)中,使用 D-Bus 类型系统进行序列化。D-Bus 提供了两种方式处理参数:
- 便捷函数:
dbus_message_append_args()和dbus_message_get_args()(适用于简单场景) - 迭代器:
DBusMessageIter(适用于复杂类型和灵活控制)
2. 添加参数
2.1 使用 dbus_message_append_args
2.1.1 函数签名
dbus_bool_t dbus_message_append_args(
DBusMessage *message,
int first_arg_type, // 第一个参数的类型
...); // 可变参数列表:类型-值对
2.1.2 基本用法
基本类型参数:
// 准备要添加的参数值
const char *str = "Hello"; // 字符串参数
int num = 42; // 整数参数
dbus_bool_t flag = TRUE; // 布尔参数
// 添加参数到消息
// 注意:每个参数需要传递类型代码和值的地址
dbus_message_append_args(
msg, // 目标消息
DBUS_TYPE_STRING, &str, // 类型:字符串,值:str 的地址
DBUS_TYPE_INT32, &num, // 类型:32位整数,值:num 的地址
DBUS_TYPE_BOOLEAN, &flag, // 类型:布尔值,值:flag 的地址
DBUS_TYPE_INVALID); // 结束标记(必需,表示参数列表结束)
参数格式说明:
- 类型-值对:每个参数由类型代码和值的地址组成
- 结束标记:最后一个参数必须是
DBUS_TYPE_INVALID,用于标识参数列表结束 - 地址传递:基本类型需要传递值的地址(
&value),因为函数内部需要读取值 - 字符串特殊处理:字符串需要传递
const char**(即&str),因为函数需要字符串指针的地址
为什么需要传递地址:
- 函数使用可变参数列表(
va_list),需要知道值的类型和位置 - 通过传递地址,函数可以读取实际的值
- 对于字符串,需要传递指针的地址,因为字符串本身就是指针类型
完整示例:
// 创建方法调用消息
DBusMessage *msg = dbus_message_new_method_call(
"com.example.Service",
"/com/example/Object",
"com.example.Interface",
"Method");
if (msg == NULL) {
fprintf(stderr, "Failed to create message\n");
return;
}
// 添加多个不同类型的参数
const char *name = "John";
int age = 30;
dbus_bool_t active = TRUE;
double salary = 50000.0;
if (!dbus_message_append_args(
msg,
DBUS_TYPE_STRING, &name, // 参数1:字符串
DBUS_TYPE_INT32, &age, // 参数2:整数
DBUS_TYPE_BOOLEAN, &active, // 参数3:布尔值
DBUS_TYPE_DOUBLE, &salary, // 参数4:浮点数
DBUS_TYPE_INVALID)) { // 结束标记
fprintf(stderr, "Failed to append arguments\n");
dbus_message_unref(msg);
return;
}
// 消息现在包含 4 个参数,可以发送
// dbus_connection_send(conn, msg, NULL);
dbus_message_unref(msg);
2.1.3 数组参数
固定长度类型数组:
// 准备数组数据
const dbus_int32_t array[] = {1, 2, 3, 4, 5};
// 注意:必须使用指针变量,不能直接使用数组名
// 因为 C 语言中,数组名在某些上下文中会退化为指针,导致 &array != array
const dbus_int32_t *array_ptr = array;
// 添加数组参数
// 参数格式:DBUS_TYPE_ARRAY, 元素类型, 数组指针的地址, 元素数量
dbus_message_append_args(
msg,
DBUS_TYPE_ARRAY, // 数组类型标识
DBUS_TYPE_INT32, // 元素类型:32位整数
&array_ptr, // 数组指针的地址(注意是 &array_ptr,不是 &array)
5, // 元素数量
DBUS_TYPE_INVALID); // 结束标记
为什么需要指针变量:
- C 语言中,
&array在某些情况下不等于array - 函数需要数组指针的地址,而不是数组本身的地址
- 使用中间指针变量可以确保类型正确
字符串数组:
// 准备字符串数组
const char *strings[] = {"one", "two", "three"};
// 字符串数组本身就是指针数组,需要传递指针的地址
const char **strings_ptr = strings;
// 添加字符串数组参数
// 参数格式:DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, 字符串数组指针的地址, 元素数量
dbus_message_append_args(
msg,
DBUS_TYPE_ARRAY, // 数组类型标识
DBUS_TYPE_STRING, // 元素类型:字符串
&strings_ptr, // 字符串数组指针的地址(三级指针)
3, // 元素数量
DBUS_TYPE_INVALID); // 结束标记
字符串数组的特殊性:
- 字符串数组是
const char*[]类型 - 需要传递
const char***(三级指针) - 函数内部会逐个复制每个字符串到消息体
完整示例:
// 示例1:整数数组
const dbus_int32_t numbers[] = {10, 20, 30, 40};
const dbus_int32_t *numbers_ptr = numbers;
dbus_message_append_args(
msg,
DBUS_TYPE_ARRAY, DBUS_TYPE_INT32, &numbers_ptr, 4,
DBUS_TYPE_INVALID);
// 示例2:字符串数组
const char *names[] = {"Alice", "Bob", "Charlie"};
const char **names_ptr = names;
dbus_message_append_args(
msg,
DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, &names_ptr, 3,
DBUS_TYPE_INVALID);
// 示例3:混合参数(数组 + 基本类型)
const dbus_uint32_t ids[] = {1, 2, 3};
const dbus_uint32_t *ids_ptr = ids;
const char *status = "active";
dbus_message_append_args(
msg,
DBUS_TYPE_ARRAY, DBUS_TYPE_UINT32, &ids_ptr, 3, // 数组参数
DBUS_TYPE_STRING, &status, // 字符串参数
DBUS_TYPE_INVALID);
限制说明:
- 仅支持固定长度基本类型的数组:如
int32[]、uint64[]、double[]等 - 不支持 Unix 文件描述符数组:需要使用迭代器手动处理
- 不支持复杂类型数组:如结构体数组、字典数组等,需要使用迭代器
- 字符串数组特殊处理:虽然支持,但需要三级指针,使用相对复杂
2.1.4 Unix 文件描述符
基本用法:
// 打开文件获取文件描述符
int fd = open("/path/to/file", O_RDONLY);
if (fd < 0) {
perror("open");
return;
}
// 添加文件描述符到消息
// 注意:传递 fd 的地址,函数内部会 dup() 这个 fd
dbus_message_append_args(
msg,
DBUS_TYPE_UNIX_FD, &fd, // 类型:Unix 文件描述符,值:fd 的地址
DBUS_TYPE_INVALID);
// 重要:可以立即关闭原始 fd
// 因为函数内部已经 dup() 了文件描述符,消息拥有自己的副本
close(fd);
工作原理说明:
-
文件描述符传递:
- D-Bus 通过 Unix 域套接字的
SCM_RIGHTS控制消息传递文件描述符 - 消息体中存储的是 fd 在消息的 fd 数组中的索引,不是实际的 fd 值
- D-Bus 通过 Unix 域套接字的
-
内部处理流程:
// 函数内部的处理(简化版) int *fds = expand_fd_array(message, 1); // 扩展消息的 fd 数组 *fds = _dbus_dup(fd, NULL); // dup() 文件描述符 u = message->n_unix_fds; // 获取索引 // 将索引写入消息体(而不是实际的 fd 值) _dbus_type_writer_write_basic(&writer, DBUS_TYPE_UNIX_FD, &u); -
为什么可以立即关闭:
- 函数内部调用
dup()创建了文件描述符的副本 - 消息拥有自己的 fd 副本,独立于原始 fd
- 原始 fd 可以立即关闭,不影响消息中的 fd
- 函数内部调用
完整示例:
// 示例1:传递单个文件描述符
int fd = open("/etc/passwd", O_RDONLY);
if (fd >= 0) {
dbus_message_append_args(
msg,
DBUS_TYPE_UNIX_FD, &fd,
DBUS_TYPE_INVALID);
close(fd); // 安全关闭
}
// 示例2:文件描述符 + 其他参数
int log_fd = open("/var/log/app.log", O_WRONLY | O_APPEND);
const char *message = "Log entry";
if (log_fd >= 0) {
dbus_message_append_args(
msg,
DBUS_TYPE_STRING, &message, // 字符串参数
DBUS_TYPE_UNIX_FD, &log_fd, // 文件描述符参数
DBUS_TYPE_INVALID);
close(log_fd); // 安全关闭
}
// 示例3:错误处理
int fd = open("/path/to/file", O_RDONLY);
if (fd < 0) {
perror("open");
return;
}
if (!dbus_message_append_args(
msg,
DBUS_TYPE_UNIX_FD, &fd,
DBUS_TYPE_INVALID)) {
// 追加失败,关闭 fd
close(fd);
fprintf(stderr, "Failed to append file descriptor\n");
return;
}
// 成功追加,可以关闭原始 fd
close(fd);
注意事项:
- 平台支持:需要编译时启用
HAVE_UNIX_FD_PASSING - 传输要求:必须使用 Unix 域套接字(AF_UNIX)
- 资源管理:消息销毁时会自动关闭所有关联的 fd
- 接收端处理:接收端会获得新的 fd(通过
recvmsg()),需要负责关闭
2.1.5 实现细节
内部实现流程:
dbus_message_append_args() 内部使用可变参数列表和消息迭代器实现:
dbus_bool_t dbus_message_append_args(DBusMessage *message,
int first_arg_type,
...)
{
dbus_bool_t retval;
va_list var_args;
// 步骤 1: 初始化可变参数列表
// va_start 用于获取可变参数的起始位置
va_start(var_args, first_arg_type);
// 步骤 2: 调用 valist 版本(便于语言绑定使用)
retval = dbus_message_append_args_valist(message,
first_arg_type,
var_args);
// 步骤 3: 清理可变参数列表
va_end(var_args);
return retval;
}
核心实现(valist 版本):
dbus_bool_t dbus_message_append_args_valist(DBusMessage *message,
int first_arg_type,
va_list var_args)
{
int type;
DBusMessageIter iter;
// 步骤 1: 初始化追加迭代器
// 将迭代器设置为写入模式,准备向消息体追加数据
dbus_message_iter_init_append(message, &iter);
// 步骤 2: 遍历可变参数列表
type = first_arg_type;
while (type != DBUS_TYPE_INVALID) {
if (dbus_type_is_basic(type)) {
// 情况 A: 基本类型(整数、字符串、布尔值等)
const void *value;
// 从可变参数列表中获取值(值的地址)
value = va_arg(var_args, const void *);
// 追加基本类型值到消息体
if (!dbus_message_iter_append_basic(&iter, type, value))
goto failed; // 内存不足
}
else if (type == DBUS_TYPE_ARRAY) {
// 情况 B: 数组类型
int element_type;
DBusMessageIter array;
char buf[2];
// 获取数组元素类型
element_type = va_arg(var_args, int);
// 构建元素类型签名字符串(用于容器)
buf[0] = element_type;
buf[1] = '\0';
// 打开数组容器
if (!dbus_message_iter_open_container(&iter,
DBUS_TYPE_ARRAY,
buf,
&array))
goto failed;
if (dbus_type_is_fixed(element_type) &&
element_type != DBUS_TYPE_UNIX_FD) {
// 情况 B1: 固定长度类型数组(如 int32[])
const void **value;
int n_elements;
// 获取数组指针和元素数量
value = va_arg(var_args, const void **);
n_elements = va_arg(var_args, int);
// 一次性追加整个数组(高效)
if (!dbus_message_iter_append_fixed_array(&array,
element_type,
value,
n_elements)) {
// 失败时放弃容器
dbus_message_iter_abandon_container(&iter, &array);
goto failed;
}
}
else if (_DBUS_TYPE_IS_STRINGLIKE(element_type)) {
// 情况 B2: 字符串类数组(STRING、OBJECT_PATH、SIGNATURE)
const char ***value_p;
const char **value;
int n_elements;
int i;
// 获取字符串数组指针(注意是三级指针)
value_p = va_arg(var_args, const char***);
n_elements = va_arg(var_args, int);
value = *value_p;
// 逐个追加字符串元素
i = 0;
while (i < n_elements) {
if (!dbus_message_iter_append_basic(&array,
element_type,
&value[i])) {
dbus_message_iter_abandon_container(&iter, &array);
goto failed;
}
++i;
}
}
else {
// 情况 B3: 不支持的数组类型(如复杂类型数组)
// 需要使用迭代器手动处理
dbus_message_iter_abandon_container(&iter, &array);
goto failed;
}
// 关闭数组容器
if (!dbus_message_iter_close_container(&iter, &array))
goto failed;
}
// 步骤 3: 获取下一个参数类型
type = va_arg(var_args, int);
}
return TRUE;
failed:
return FALSE;
}
关键实现细节说明:
-
可变参数处理:
- 使用
va_list、va_start、va_arg、va_end处理可变参数 - 参数格式:
类型1, 值1, 类型2, 值2, ..., DBUS_TYPE_INVALID - 每个基本类型需要传递值的地址(
&value)
- 使用
-
迭代器使用:
- 使用
dbus_message_iter_init_append()初始化追加迭代器 - 所有参数通过迭代器追加到消息体
- 迭代器自动处理内存分配和对齐
- 使用
-
数组处理:
- 固定长度类型:使用
append_fixed_array()一次性追加(高效) - 字符串类型:逐个追加每个字符串元素
- 复杂类型:不支持,需要使用迭代器手动处理
- 固定长度类型:使用
-
错误处理:
- 任何步骤失败都会跳转到
failed标签 - 数组处理失败时会调用
abandon_container()清理 - 返回
FALSE表示失败
- 任何步骤失败都会跳转到
-
内存管理:
- 字符串值会被复制到消息体中
- Unix 文件描述符会被
dup() - 失败时消息可能处于不一致状态,需要重新创建
性能考虑:
- 固定长度类型数组使用批量追加,比逐个追加更高效
- 字符串数组需要逐个追加,因为每个字符串长度不同
- 迭代器内部使用
DBusString,支持自动扩展
2.2 使用消息迭代器
2.2.1 初始化追加迭代器
DBusMessageIter iter;
// 初始化迭代器用于追加模式
// 迭代器会被设置为写入模式,准备向消息体追加数据
dbus_message_iter_init_append(message, &iter);
// 注意:
// - 迭代器分配在栈上,不需要手动释放
// - 可以多次调用此函数重新初始化迭代器
// - 每次调用都会重置迭代器到消息体末尾
初始化说明:
- 写入模式:迭代器被设置为
DBUS_MESSAGE_ITER_TYPE_WRITER - 位置:迭代器指向消息体末尾,新数据会追加到末尾
- 字节序:自动使用编译器的字节序
- 签名管理:迭代器会自动管理类型签名
2.2.2 追加基本类型
DBusMessageIter iter;
dbus_message_iter_init_append(message, &iter);
// 追加字符串
// 参数:迭代器、类型代码、值的地址
const char *str = "Hello";
if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &str)) {
fprintf(stderr, "Failed to append string\n");
return;
}
// 说明:字符串会被复制到消息体中,str 可以立即修改或释放
// 追加整数
// 注意:传递整数的地址
int num = 42;
if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_INT32, &num)) {
fprintf(stderr, "Failed to append integer\n");
return;
}
// 说明:整数值会被复制到消息体中
// 追加布尔值
// 注意:dbus_bool_t 实际上是 uint32_t
dbus_bool_t flag = TRUE;
if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &flag)) {
fprintf(stderr, "Failed to append boolean\n");
return;
}
// 说明:布尔值在 D-Bus 中是 4 字节的 uint32(0 或 1)
// 追加浮点数
double value = 3.14159;
if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_DOUBLE, &value)) {
fprintf(stderr, "Failed to append double\n");
return;
}
关键点说明:
- 地址传递:所有基本类型都需要传递值的地址(
&value) - 返回值检查:函数返回
FALSE表示失败(通常是内存不足) - 数据复制:值会被复制到消息体中,原始变量可以修改
- 字符串处理:字符串会被复制,包括 null 终止符
- 类型验证:函数内部会验证类型和值的有效性(如 UTF-8 编码)
2.2.3 追加数组
基本数组追加:
DBusMessageIter iter, sub;
dbus_message_iter_init_append(message, &iter);
// 步骤 1: 打开数组容器
// 参数:父迭代器、容器类型、元素类型签名、子迭代器
// "i" 表示元素类型是 int32
if (!dbus_message_iter_open_container(&iter,
DBUS_TYPE_ARRAY, // 容器类型:数组
"i", // 元素类型签名:int32
&sub)) { // 子迭代器(输出)
fprintf(stderr, "Failed to open array container\n");
return;
}
// 步骤 2: 添加数组元素
// 使用子迭代器追加每个元素
int values[] = {1, 2, 3, 4, 5};
for (int i = 0; i < 5; i++) {
// 注意:传递 values[i] 的地址
if (!dbus_message_iter_append_basic(&sub,
DBUS_TYPE_INT32,
&values[i])) {
// 追加失败,放弃容器
dbus_message_iter_abandon_container(&iter, &sub);
fprintf(stderr, "Failed to append array element\n");
return;
}
}
// 步骤 3: 关闭数组容器
// 必须使用与 open_container 时相同的迭代器对
if (!dbus_message_iter_close_container(&iter, &sub)) {
fprintf(stderr, "Failed to close array container\n");
return;
}
// 说明:关闭容器会完成数组的序列化,包括长度和对齐
错误处理示例:
DBusMessageIter iter, sub = DBUS_MESSAGE_ITER_INIT_CLOSED;
dbus_message_iter_init_append(message, &iter);
if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "i", &sub)) {
// 打开失败,直接返回
return;
}
// 追加元素...
int values[] = {1, 2, 3};
for (int i = 0; i < 3; i++) {
if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_INT32, &values[i])) {
// 追加失败,放弃容器
dbus_message_iter_abandon_container(&iter, &sub);
return;
}
}
// 关闭容器
if (!dbus_message_iter_close_container(&iter, &sub)) {
// 关闭失败(内存不足),但容器已经部分写入
// 消息可能处于不一致状态
return;
}
字符串数组追加:
DBusMessageIter iter, sub;
dbus_message_iter_init_append(message, &iter);
// 打开字符串数组容器
// "s" 表示元素类型是字符串
if (!dbus_message_iter_open_container(&iter,
DBUS_TYPE_ARRAY,
"s", // 字符串类型签名
&sub)) {
return;
}
// 添加字符串元素
const char *names[] = {"Alice", "Bob", "Charlie"};
for (int i = 0; i < 3; i++) {
// 注意:传递 names[i] 的地址(const char**)
if (!dbus_message_iter_append_basic(&sub,
DBUS_TYPE_STRING,
&names[i])) {
dbus_message_iter_abandon_container(&iter, &sub);
return;
}
}
// 关闭容器
if (!dbus_message_iter_close_container(&iter, &sub)) {
return;
}
关键点说明:
- 容器操作必须配对:
open_container和close_container必须使用相同的迭代器对 - 元素类型签名:必须提供正确的元素类型签名字符串
- 错误处理:任何步骤失败都应该调用
abandon_container()清理 - 内存管理:容器内部会自动处理内存分配和对齐
- 性能考虑:对于固定长度类型数组,使用
append_fixed_array()更高效
2.2.4 追加结构体
基本结构体追加:
DBusMessageIter iter, sub;
dbus_message_iter_init_append(message, &iter);
// 步骤 1: 打开结构体容器
// 参数:父迭代器、容器类型、类型签名(NULL 表示自动推断)、子迭代器
// 注意:结构体的类型签名为 NULL,因为会根据实际添加的字段自动推断
if (!dbus_message_iter_open_container(&iter,
DBUS_TYPE_STRUCT, // 容器类型:结构体
NULL, // 类型签名:自动推断
&sub)) { // 子迭代器(输出)
fprintf(stderr, "Failed to open struct container\n");
return;
}
// 步骤 2: 添加结构体字段(按顺序)
// 结构体字段必须按顺序添加,不能跳过
const char *name = "John";
int age = 30;
// 字段1:字符串(姓名)
if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &name)) {
dbus_message_iter_abandon_container(&iter, &sub);
return;
}
// 字段2:整数(年龄)
if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_INT32, &age)) {
dbus_message_iter_abandon_container(&iter, &sub);
return;
}
// 步骤 3: 关闭结构体容器
// 关闭时会自动完成结构体的序列化
if (!dbus_message_iter_close_container(&iter, &sub)) {
fprintf(stderr, "Failed to close struct container\n");
return;
}
// 说明:结构体在 D-Bus 中不需要对齐填充,字段连续存储
复杂结构体示例:
// 结构体:(string, int32, boolean, double)
// 表示:姓名、年龄、是否激活、工资
DBusMessageIter iter, sub;
dbus_message_iter_init_append(message, &iter);
if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_STRUCT, NULL, &sub)) {
return;
}
// 字段1:姓名(字符串)
const char *name = "John Doe";
if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &name)) {
dbus_message_iter_abandon_container(&iter, &sub);
return;
}
// 字段2:年龄(整数)
int age = 30;
if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_INT32, &age)) {
dbus_message_iter_abandon_container(&iter, &sub);
return;
}
// 字段3:是否激活(布尔值)
dbus_bool_t active = TRUE;
if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_BOOLEAN, &active)) {
dbus_message_iter_abandon_container(&iter, &sub);
return;
}
// 字段4:工资(浮点数)
double salary = 50000.0;
if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_DOUBLE, &salary)) {
dbus_message_iter_abandon_container(&iter, &sub);
return;
}
// 关闭结构体
if (!dbus_message_iter_close_container(&iter, &sub)) {
return;
}
嵌套结构体示例:
// 外层结构体:(string, (int32, string))
// 表示:姓名、地址(邮编、街道)
DBusMessageIter iter, outer, inner;
dbus_message_iter_init_append(message, &iter);
// 打开外层结构体
if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_STRUCT, NULL, &outer)) {
return;
}
// 字段1:姓名
const char *name = "John";
if (!dbus_message_iter_append_basic(&outer, DBUS_TYPE_STRING, &name)) {
dbus_message_iter_abandon_container(&iter, &outer);
return;
}
// 字段2:地址(嵌套结构体)
if (!dbus_message_iter_open_container(&outer, DBUS_TYPE_STRUCT, NULL, &inner)) {
dbus_message_iter_abandon_container(&iter, &outer);
return;
}
// 地址字段1:邮编
int zipcode = 12345;
if (!dbus_message_iter_append_basic(&inner, DBUS_TYPE_INT32, &zipcode)) {
dbus_message_iter_abandon_container(&outer, &inner);
dbus_message_iter_abandon_container(&iter, &outer);
return;
}
// 地址字段2:街道
const char *street = "Main St";
if (!dbus_message_iter_append_basic(&inner, DBUS_TYPE_STRING, &street)) {
dbus_message_iter_abandon_container(&outer, &inner);
dbus_message_iter_abandon_container(&iter, &outer);
return;
}
// 关闭内层结构体
if (!dbus_message_iter_close_container(&outer, &inner)) {
dbus_message_iter_abandon_container(&iter, &outer);
return;
}
// 关闭外层结构体
if (!dbus_message_iter_close_container(&iter, &outer)) {
return;
}
关键点说明:
- 类型签名为 NULL:结构体的类型签名由实际添加的字段自动推断
- 字段顺序:字段必须按顺序添加,不能跳过中间字段
- 嵌套支持:结构体可以嵌套,形成复杂的层次结构
- 对齐规则:结构体字段按类型对齐,但结构体本身不需要填充
- 错误处理:嵌套结构体失败时需要按相反顺序放弃所有容器
2.2.5 追加字典
字典结构说明:
- D-Bus 字典是
array of dict_entry类型 - 每个
dict_entry是struct(key, value)结构 - 键通常是字符串,值可以是任意类型(通常使用 Variant)
基本字典追加:
DBusMessageIter iter, dict, entry;
dbus_message_iter_init_append(message, &iter);
// 步骤 1: 打开字典容器(数组)
// "{sv}" 表示:数组的元素是 dict_entry,键是字符串(s),值是 Variant(v)
if (!dbus_message_iter_open_container(&iter,
DBUS_TYPE_ARRAY, // 容器类型:数组
"{sv}", // 元素类型:dict_entry<string, variant>
&dict)) { // 字典迭代器(输出)
fprintf(stderr, "Failed to open dict container\n");
return;
}
// 步骤 2: 添加第一个字典条目
if (!dbus_message_iter_open_container(&dict,
DBUS_TYPE_DICT_ENTRY, // 容器类型:字典条目
NULL, // 类型签名:自动推断(struct(key, value))
&entry)) { // 条目迭代器(输出)
dbus_message_iter_abandon_container(&iter, &dict);
return;
}
// 步骤 2.1: 添加键(字符串)
const char *key1 = "name";
if (!dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &key1)) {
dbus_message_iter_abandon_container(&dict, &entry);
dbus_message_iter_abandon_container(&iter, &dict);
return;
}
// 步骤 2.2: 添加值(Variant,包含字符串)
DBusMessageIter variant;
if (!dbus_message_iter_open_container(&entry,
DBUS_TYPE_VARIANT, // 容器类型:变体
"s", // 值的类型签名:字符串
&variant)) { // 变体迭代器(输出)
dbus_message_iter_abandon_container(&dict, &entry);
dbus_message_iter_abandon_container(&iter, &dict);
return;
}
const char *value1 = "John";
if (!dbus_message_iter_append_basic(&variant, DBUS_TYPE_STRING, &value1)) {
dbus_message_iter_abandon_container(&entry, &variant);
dbus_message_iter_abandon_container(&dict, &entry);
dbus_message_iter_abandon_container(&iter, &dict);
return;
}
// 关闭 Variant
if (!dbus_message_iter_close_container(&entry, &variant)) {
dbus_message_iter_abandon_container(&dict, &entry);
dbus_message_iter_abandon_container(&iter, &dict);
return;
}
// 关闭字典条目
if (!dbus_message_iter_close_container(&dict, &entry)) {
dbus_message_iter_abandon_container(&iter, &dict);
return;
}
// 步骤 3: 添加第二个字典条目(可以添加多个)
if (!dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry)) {
dbus_message_iter_abandon_container(&iter, &dict);
return;
}
const char *key2 = "age";
if (!dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &key2)) {
dbus_message_iter_abandon_container(&dict, &entry);
dbus_message_iter_abandon_container(&iter, &dict);
return;
}
// 值:Variant,包含整数
if (!dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, "i", &variant)) {
dbus_message_iter_abandon_container(&dict, &entry);
dbus_message_iter_abandon_container(&iter, &dict);
return;
}
int age_value = 30;
if (!dbus_message_iter_append_basic(&variant, DBUS_TYPE_INT32, &age_value)) {
dbus_message_iter_abandon_container(&entry, &variant);
dbus_message_iter_abandon_container(&dict, &entry);
dbus_message_iter_abandon_container(&iter, &dict);
return;
}
if (!dbus_message_iter_close_container(&entry, &variant)) {
dbus_message_iter_abandon_container(&dict, &entry);
dbus_message_iter_abandon_container(&iter, &dict);
return;
}
if (!dbus_message_iter_close_container(&dict, &entry)) {
dbus_message_iter_abandon_container(&iter, &dict);
return;
}
// 步骤 4: 关闭字典容器
if (!dbus_message_iter_close_container(&iter, &dict)) {
fprintf(stderr, "Failed to close dict container\n");
return;
}
辅助函数封装:
// 辅助函数:添加字典条目(简化错误处理)
dbus_bool_t append_dict_entry(DBusMessageIter *dict,
const char *key,
const char *value_type,
const void *value)
{
DBusMessageIter entry, variant;
// 打开字典条目
if (!dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry))
return FALSE;
// 添加键
if (!dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &key)) {
dbus_message_iter_abandon_container(dict, &entry);
return FALSE;
}
// 添加值(Variant)
if (!dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, value_type, &variant)) {
dbus_message_iter_abandon_container(dict, &entry);
return FALSE;
}
// 根据类型添加值
int type = value_type[0];
if (!dbus_message_iter_append_basic(&variant, type, value)) {
dbus_message_iter_abandon_container(&entry, &variant);
dbus_message_iter_abandon_container(dict, &entry);
return FALSE;
}
// 关闭 Variant
if (!dbus_message_iter_close_container(&entry, &variant)) {
dbus_message_iter_abandon_container(dict, &entry);
return FALSE;
}
// 关闭字典条目
return dbus_message_iter_close_container(dict, &entry);
}
// 使用示例
DBusMessageIter iter, dict;
dbus_message_iter_init_append(message, &iter);
if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &dict))
return;
// 添加多个条目
const char *name = "John";
int age = 30;
dbus_bool_t active = TRUE;
if (!append_dict_entry(&dict, "name", "s", &name) ||
!append_dict_entry(&dict, "age", "i", &age) ||
!append_dict_entry(&dict, "active", "b", &active)) {
dbus_message_iter_abandon_container(&iter, &dict);
return;
}
if (!dbus_message_iter_close_container(&iter, &dict))
return;
关键点说明:
- 字典是数组:字典实际上是
array of dict_entry,不是独立类型 - 类型签名格式:
"{sv}"表示array<dict_entry<string, variant>> - Variant 使用:值通常使用 Variant,允许不同类型混合
- 嵌套层次:字典 → 条目 → Variant → 值,共 4 层
- 错误处理:失败时需要按相反顺序放弃所有容器
- 性能考虑:字典条目可以按任意顺序添加,但通常按键排序
3. 读取参数
3.1 使用 dbus_message_get_args
3.1.1 函数签名
dbus_bool_t dbus_message_get_args(
DBusMessage *message,
DBusError *error,
int first_arg_type,
...);
3.1.2 基本用法
基本类型参数读取:
// 步骤 1: 声明变量用于存储读取的值
const char *str; // 字符串指针(指向消息内部数据)
int num; // 整数变量
DBusError error; // 错误对象
// 步骤 2: 初始化错误对象(必须)
dbus_error_init(&error);
// 步骤 3: 读取参数
// 参数格式:消息、错误对象、类型1、地址1、类型2、地址2、...、DBUS_TYPE_INVALID
if (dbus_message_get_args(
msg, // 要读取的消息
&error, // 错误对象(用于接收错误信息)
DBUS_TYPE_STRING, &str, // 类型:字符串,存储位置:str 的地址
DBUS_TYPE_INT32, &num, // 类型:整数,存储位置:num 的地址
DBUS_TYPE_INVALID)) { // 结束标记
// 读取成功,使用参数
printf("Received: %s, %d\n", str, num);
// 注意:str 指向消息内部数据,消息释放后无效
// num 是值的副本,可以安全使用
} else {
// 读取失败,检查错误
if (dbus_error_is_set(&error)) {
fprintf(stderr, "Error: %s\n", error.message);
dbus_error_free(&error); // 必须释放错误对象
}
return;
}
参数格式说明:
- 类型-地址对:每个参数由类型代码和存储地址组成
- 地址传递:需要传递变量的地址(
&variable),函数会将值写入该地址 - 结束标记:最后一个参数必须是
DBUS_TYPE_INVALID - 错误处理:必须初始化
DBusError,失败时必须释放
内存管理说明:
- 字符串指针:指向消息内部数据,不需要释放,但消息释放后无效
- 数值类型:是值的副本,可以安全使用和修改
- 数组指针:指向消息内部数据,不需要释放
完整示例:
// 示例:读取多个不同类型的参数
const char *name;
int age;
dbus_bool_t active;
double salary;
DBusError error;
dbus_error_init(&error);
if (dbus_message_get_args(
msg, &error,
DBUS_TYPE_STRING, &name, // 参数1:字符串
DBUS_TYPE_INT32, &age, // 参数2:整数
DBUS_TYPE_BOOLEAN, &active, // 参数3:布尔值
DBUS_TYPE_DOUBLE, &salary, // 参数4:浮点数
DBUS_TYPE_INVALID)) {
// 成功读取所有参数
printf("Name: %s\n", name);
printf("Age: %d\n", age);
printf("Active: %s\n", active ? "yes" : "no");
printf("Salary: %.2f\n", salary);
// 注意:name 指向消息内部,不要释放
// age、active、salary 是值的副本,可以安全使用
} else {
// 读取失败
if (dbus_error_is_set(&error)) {
fprintf(stderr, "Failed to get args: %s\n", error.message);
dbus_error_free(&error);
}
return;
}
3.1.3 读取数组
固定长度类型数组:
// 声明变量
int array_len; // 数组长度(输出)
const dbus_int32_t *array; // 数组指针(输出,指向消息内部)
DBusError error;
dbus_error_init(&error);
// 读取数组参数
// 参数格式:DBUS_TYPE_ARRAY, 元素类型, 数组指针地址, 长度指针地址
if (dbus_message_get_args(
msg, &error,
DBUS_TYPE_ARRAY, // 数组类型标识
DBUS_TYPE_INT32, // 元素类型:32位整数
&array, // 数组指针的地址(输出)
&array_len, // 数组长度的地址(输出)
DBUS_TYPE_INVALID)) {
// 读取成功,遍历数组
printf("Array length: %d\n", array_len);
for (int i = 0; i < array_len; i++) {
printf("Array[%d] = %d\n", i, array[i]);
}
// 注意:
// - array 指向消息内部数据,不需要释放
// - 消息释放后 array 无效
// - array_len 是值的副本,可以安全使用
} else {
if (dbus_error_is_set(&error)) {
fprintf(stderr, "Error: %s\n", error.message);
dbus_error_free(&error);
}
}
字符串数组:
// 声明变量
int array_len; // 数组长度(输出)
const char **array; // 字符串数组指针(输出,需要释放)
DBusError error;
dbus_error_init(&error);
// 读取字符串数组
if (dbus_message_get_args(
msg, &error,
DBUS_TYPE_ARRAY, // 数组类型
DBUS_TYPE_STRING, // 元素类型:字符串
&array, // 字符串数组指针的地址(输出)
&array_len, // 数组长度的地址(输出)
DBUS_TYPE_INVALID)) {
// 读取成功,遍历字符串数组
printf("String array length: %d\n", array_len);
for (int i = 0; i < array_len; i++) {
printf("Array[%d] = %s\n", i, array[i]);
}
// 重要:字符串数组需要释放
// 注意:array 本身需要释放,但 array[i] 指向消息内部,不需要释放
dbus_free_string_array(array);
} else {
if (dbus_error_is_set(&error)) {
fprintf(stderr, "Error: %s\n", error.message);
dbus_error_free(&error);
}
}
字符串数组内存管理说明:
- 数组指针:
array是函数分配的内存,需要调用dbus_free_string_array()释放 - 字符串指针:
array[i]指向消息内部数据,不需要单独释放 - 释放时机:使用完数组后立即释放,或在消息释放前释放
完整示例:
// 示例1:读取整数数组
int ids_len;
const dbus_uint32_t *ids;
if (dbus_message_get_args(
msg, NULL, // 不使用错误对象
DBUS_TYPE_ARRAY, DBUS_TYPE_UINT32, &ids, &ids_len,
DBUS_TYPE_INVALID)) {
for (int i = 0; i < ids_len; i++) {
printf("ID[%d] = %u\n", i, ids[i]);
}
// ids 指向消息内部,不需要释放
}
// 示例2:读取字符串数组
int names_len;
const char **names;
DBusError error;
dbus_error_init(&error);
if (dbus_message_get_args(
msg, &error,
DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, &names, &names_len,
DBUS_TYPE_INVALID)) {
for (int i = 0; i < names_len; i++) {
printf("Name[%d] = %s\n", i, names[i]);
}
// 必须释放 names
dbus_free_string_array(names);
} else {
if (dbus_error_is_set(&error)) {
fprintf(stderr, "Error: %s\n", error.message);
dbus_error_free(&error);
}
}
// 示例3:混合参数(数组 + 基本类型)
int count;
const dbus_int32_t *numbers;
const char *status;
if (dbus_message_get_args(
msg, NULL,
DBUS_TYPE_ARRAY, DBUS_TYPE_INT32, &numbers, &count, // 数组参数
DBUS_TYPE_STRING, &status, // 字符串参数
DBUS_TYPE_INVALID)) {
printf("Status: %s\n", status);
printf("Numbers count: %d\n", count);
for (int i = 0; i < count; i++) {
printf("Number[%d] = %d\n", i, numbers[i]);
}
}
3.1.4 实现细节
内部实现流程:
dbus_message_get_args() 内部使用可变参数列表和消息迭代器实现:
dbus_bool_t dbus_message_get_args(DBusMessage *message,
DBusError *error,
int first_arg_type,
...)
{
dbus_bool_t retval;
va_list var_args;
// 步骤 1: 参数验证
_dbus_return_val_if_fail(message != NULL, FALSE);
_dbus_return_val_if_error_is_set(error, FALSE); // 如果 error 已设置,直接返回
// 步骤 2: 初始化可变参数列表
va_start(var_args, first_arg_type);
// 步骤 3: 调用 valist 版本
retval = dbus_message_get_args_valist(message, error, first_arg_type, var_args);
// 步骤 4: 清理可变参数列表
va_end(var_args);
return retval;
}
核心实现(valist 版本):
dbus_bool_t dbus_message_get_args_valist(DBusMessage *message,
DBusError *error,
int first_arg_type,
va_list var_args)
{
DBusMessageIter iter;
// 步骤 1: 初始化读取迭代器
// 如果消息没有参数,返回 FALSE
dbus_message_iter_init(message, &iter);
// 步骤 2: 调用迭代器版本的参数获取函数
return _dbus_message_iter_get_args_valist(&iter, error, first_arg_type, var_args);
}
迭代器参数获取实现:
dbus_bool_t _dbus_message_iter_get_args_valist(DBusMessageIter *iter,
DBusError *error,
int first_arg_type,
va_list var_args)
{
DBusMessageRealIter *real = (DBusMessageRealIter *)iter;
int spec_type, msg_type, i;
dbus_bool_t retval;
va_list copy_args;
// 步骤 1: 验证迭代器有效性
_dbus_assert(_dbus_message_iter_check(real));
retval = FALSE;
spec_type = first_arg_type;
i = 0;
// 步骤 2: 复制可变参数列表(用于错误处理时的清理)
va_copy(copy_args, var_args);
// 步骤 3: 遍历参数列表
while (spec_type != DBUS_TYPE_INVALID) {
// 步骤 3.1: 获取消息中当前参数的实际类型
msg_type = dbus_message_iter_get_arg_type(iter);
// 步骤 3.2: 验证类型匹配
if (msg_type != spec_type) {
// 类型不匹配,设置错误信息
dbus_set_error(error, DBUS_ERROR_INVALID_ARGS,
"Argument %d is specified to be of type \"%s\", but "
"is actually of type \"%s\"\n",
i,
_dbus_type_to_string(spec_type),
_dbus_type_to_string(msg_type));
goto out;
}
// 步骤 3.3: 根据类型读取值
if (spec_type == DBUS_TYPE_UNIX_FD) {
// 情况 A: Unix 文件描述符
#ifdef HAVE_UNIX_FD_PASSING
DBusBasicValue idx;
int *pfd, nfd;
// 获取存储 fd 的指针
pfd = va_arg(var_args, int*);
_dbus_assert(pfd);
// 读取 fd 索引(消息体中存储的是索引,不是实际的 fd)
_dbus_type_reader_read_basic(&real->u.reader, &idx);
// 验证索引有效性
if (idx.u32 >= real->message->n_unix_fds) {
dbus_set_error(error, DBUS_ERROR_INCONSISTENT_MESSAGE,
"Message refers to file descriptor at index %i, "
"but has only %i descriptors attached.\n",
idx.u32, real->message->n_unix_fds);
goto out;
}
// dup() 文件描述符(调用者负责关闭)
if ((nfd = _dbus_dup(real->message->unix_fds[idx.u32], error)) < 0)
goto out;
*pfd = nfd;
#else
dbus_set_error(error, DBUS_ERROR_NOT_SUPPORTED,
"Platform does not support file descriptor passing.\n");
goto out;
#endif
}
else if (dbus_type_is_basic(spec_type)) {
// 情况 B: 基本类型(整数、字符串、布尔值等)
void *ptr;
// 获取存储值的指针
ptr = va_arg(var_args, void *);
_dbus_assert(ptr != NULL);
// 从消息体中读取基本类型值
// 注意:字符串指针指向消息内部数据,不需要释放
_dbus_type_reader_read_basic(&real->u.reader, ptr);
}
else if (spec_type == DBUS_TYPE_ARRAY) {
// 情况 C: 数组类型
int element_type;
int spec_element_type;
const void **ptr;
int *n_elements_p;
DBusTypeReader array;
// 获取期望的元素类型
spec_element_type = va_arg(var_args, int);
// 获取消息中数组的实际元素类型
element_type = _dbus_type_reader_get_element_type(&real->u.reader);
// 验证元素类型匹配
if (spec_element_type != element_type) {
dbus_set_error(error, DBUS_ERROR_INVALID_ARGS,
"Argument %d is specified to be an array of \"%s\", but "
"is actually an array of \"%s\"\n",
i,
_dbus_type_to_string(spec_element_type),
_dbus_type_to_string(element_type));
goto out;
}
if (dbus_type_is_fixed(spec_element_type) &&
element_type != DBUS_TYPE_UNIX_FD) {
// 情况 C1: 固定长度类型数组
ptr = va_arg(var_args, const void **);
n_elements_p = va_arg(var_args, int*);
_dbus_assert(ptr != NULL);
_dbus_assert(n_elements_p != NULL);
// 进入数组,读取整个数组
_dbus_type_reader_recurse(&real->u.reader, &array);
_dbus_type_reader_get_fixed_array(&array, element_type,
ptr, n_elements_p);
// 注意:数组指针指向消息内部数据,不需要释放
}
else if (_DBUS_TYPE_IS_STRINGLIKE(element_type)) {
// 情况 C2: 字符串类数组
const char ***ptr_p;
const char **ptr;
int *n_elements_p;
DBusTypeReader array;
int j;
ptr_p = va_arg(var_args, const char***);
n_elements_p = va_arg(var_args, int*);
// 进入数组
_dbus_type_reader_recurse(&real->u.reader, &array);
// 计算数组元素数量
j = 0;
while (_dbus_type_reader_get_current_type(&array) != DBUS_TYPE_INVALID) {
j++;
_dbus_type_reader_next(&array);
}
*n_elements_p = j;
// 分配字符串数组(需要调用者释放)
ptr = dbus_new(const char*, j);
if (ptr == NULL) {
_DBUS_SET_OOM(error);
goto out;
}
// 重新进入数组,读取每个字符串
_dbus_type_reader_recurse(&real->u.reader, &array);
j = 0;
while (_dbus_type_reader_get_current_type(&array) != DBUS_TYPE_INVALID) {
_dbus_type_reader_read_basic(&array, &ptr[j]);
_dbus_type_reader_next(&array);
j++;
}
*ptr_p = ptr;
}
else {
// 情况 C3: 不支持的数组类型
dbus_set_error(error, DBUS_ERROR_INVALID_ARGS,
"Arrays of %s are not supported by dbus_message_get_args()",
_dbus_type_to_string(element_type));
goto out;
}
}
// 步骤 3.4: 移动到下一个参数
dbus_message_iter_next(iter);
spec_type = va_arg(var_args, int);
i++;
}
retval = TRUE;
out:
// 错误处理:如果失败,需要清理已分配的资源
// (如字符串数组)
va_end(copy_args);
return retval;
}
关键实现细节说明:
-
类型验证:
- 每个参数都会验证类型是否匹配
- 类型不匹配会设置详细的错误信息
- 错误信息包含参数索引和期望/实际类型
-
基本类型读取:
- 使用
_dbus_type_reader_read_basic()读取值 - 字符串指针指向消息内部数据,不需要释放
- 数值类型直接复制到提供的地址
- 使用
-
数组读取:
- 固定长度类型:使用
get_fixed_array()一次性读取 - 字符串数组:需要分配内存存储字符串指针数组
- 复杂类型:不支持,需要使用迭代器手动处理
- 固定长度类型:使用
-
Unix 文件描述符处理:
- 消息体中存储的是 fd 索引,不是实际的 fd
- 读取时会
dup()文件描述符 - 调用者负责关闭返回的 fd
-
错误处理:
- 任何步骤失败都会设置错误信息
- 已分配的资源会被正确清理
- 返回
FALSE表示失败
-
内存管理:
- 基本类型值直接复制到提供的地址
- 字符串指针指向消息内部,不需要释放
- 字符串数组需要调用者释放(使用
dbus_free_string_array()) - 固定长度类型数组指针指向消息内部,不需要释放
3.2 使用消息迭代器
3.2.1 初始化读取迭代器
DBusMessageIter iter;
// 初始化迭代器用于读取模式
// 返回值:TRUE 表示消息有参数,FALSE 表示消息没有参数
if (!dbus_message_iter_init(message, &iter)) {
// 消息没有参数,可以直接返回
printf("Message has no arguments\n");
return;
}
// 迭代器已初始化,指向第一个参数
// 注意:
// - 迭代器分配在栈上,不需要手动释放
// - 迭代器指向消息体中的第一个参数
// - 可以安全地复制迭代器(通过赋值)
初始化说明:
- 读取模式:迭代器被设置为
DBUS_MESSAGE_ITER_TYPE_READER - 位置:迭代器指向消息体的第一个参数
- 字节序:如果消息字节序与编译器不同,会自动转换
- 签名获取:从消息头获取类型签名,用于验证参数类型
3.2.2 读取基本类型
基本类型读取流程:
DBusMessageIter iter;
if (!dbus_message_iter_init(message, &iter)) {
return; // 消息没有参数
}
// 步骤 1: 获取当前参数的类型
int type = dbus_message_iter_get_arg_type(&iter);
// 步骤 2: 根据类型读取值
if (type == DBUS_TYPE_STRING) {
// 情况 A: 字符串类型
const char *str;
// 读取字符串值
// 注意:传递 str 的地址,函数会将字符串指针写入
dbus_message_iter_get_basic(&iter, &str);
// 使用字符串
printf("String: %s\n", str);
// 注意:str 指向消息内部数据,不需要释放
// 消息释放后 str 无效
}
else if (type == DBUS_TYPE_INT32) {
// 情况 B: 整数类型
dbus_int32_t num;
// 读取整数值
dbus_message_iter_get_basic(&iter, &num);
printf("Integer: %d\n", num);
// num 是值的副本,可以安全使用和修改
}
else if (type == DBUS_TYPE_BOOLEAN) {
// 情况 C: 布尔类型
dbus_bool_t flag;
dbus_message_iter_get_basic(&iter, &flag);
printf("Boolean: %s\n", flag ? "true" : "false");
}
// 步骤 3: 移动到下一个参数
// 注意:读取完当前参数后,必须调用 next() 移动到下一个参数
dbus_message_iter_next(&iter);
完整遍历示例:
DBusMessageIter iter;
if (!dbus_message_iter_init(message, &iter)) {
printf("Message has no arguments\n");
return;
}
// 遍历所有参数
int arg_index = 0;
do {
int type = dbus_message_iter_get_arg_type(&iter);
if (type == DBUS_TYPE_INVALID) {
break; // 没有更多参数
}
printf("Argument %d: type = %c\n", arg_index, type);
// 根据类型读取值
switch (type) {
case DBUS_TYPE_STRING: {
const char *str;
dbus_message_iter_get_basic(&iter, &str);
printf(" Value: %s\n", str);
break;
}
case DBUS_TYPE_INT32: {
dbus_int32_t num;
dbus_message_iter_get_basic(&iter, &num);
printf(" Value: %d\n", num);
break;
}
case DBUS_TYPE_BOOLEAN: {
dbus_bool_t flag;
dbus_message_iter_get_basic(&iter, &flag);
printf(" Value: %s\n", flag ? "true" : "false");
break;
}
case DBUS_TYPE_DOUBLE: {
double value;
dbus_message_iter_get_basic(&iter, &value);
printf(" Value: %f\n", value);
break;
}
default:
printf(" Value: (unsupported type)\n");
break;
}
arg_index++;
} while (dbus_message_iter_next(&iter));
printf("Total arguments: %d\n", arg_index);
关键点说明:
- 类型检查:读取前应该检查类型,避免类型不匹配
- 地址传递:
get_basic()需要传递存储地址(&variable) - 移动到下一个:读取完参数后必须调用
next()移动到下一个参数 - 内存管理:字符串指针指向消息内部,不需要释放
- 迭代器状态:迭代器可以安全复制,但修改消息会使迭代器失效
3.2.3 读取数组
基本数组读取:
DBusMessageIter iter;
if (!dbus_message_iter_init(message, &iter)) {
return;
}
// 步骤 1: 检查当前参数是否为数组
int type = dbus_message_iter_get_arg_type(&iter);
if (type == DBUS_TYPE_ARRAY) {
DBusMessageIter sub;
// 步骤 2: 获取数组元素类型
int element_type = dbus_message_iter_get_element_type(&iter);
printf("Array element type: %c\n", element_type);
// 步骤 3: 进入数组(创建子迭代器)
// recurse() 会创建一个新的迭代器,指向数组的第一个元素
dbus_message_iter_recurse(&iter, &sub);
// 步骤 4: 遍历数组元素
int element_index = 0;
while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
// 检查元素类型(应该与 element_type 匹配)
int current_type = dbus_message_iter_get_arg_type(&sub);
if (current_type == DBUS_TYPE_INT32) {
// 读取整数元素
dbus_int32_t value;
dbus_message_iter_get_basic(&sub, &value);
printf("Array[%d] = %d\n", element_index, value);
}
else if (current_type == DBUS_TYPE_STRING) {
// 读取字符串元素
const char *str;
dbus_message_iter_get_basic(&sub, &str);
printf("Array[%d] = %s\n", element_index, str);
}
// 移动到下一个元素
dbus_message_iter_next(&sub);
element_index++;
}
printf("Array has %d elements\n", element_index);
// 步骤 5: 移动到下一个参数(退出数组)
dbus_message_iter_next(&iter);
}
固定长度类型数组优化:
// 对于固定长度类型数组,可以使用 get_fixed_array() 一次性读取
int type = dbus_message_iter_get_arg_type(&iter);
if (type == DBUS_TYPE_ARRAY) {
int element_type = dbus_message_iter_get_element_type(&iter);
if (element_type == DBUS_TYPE_INT32) {
// 使用固定数组读取(更高效)
const dbus_int32_t *array;
int n_elements;
// 一次性读取整个数组
dbus_message_iter_get_fixed_array(&iter, &array, &n_elements);
printf("Array length: %d\n", n_elements);
for (int i = 0; i < n_elements; i++) {
printf("Array[%d] = %d\n", i, array[i]);
}
// 注意:array 指向消息内部,不需要释放
}
dbus_message_iter_next(&iter);
}
字符串数组读取:
int type = dbus_message_iter_get_arg_type(&iter);
if (type == DBUS_TYPE_ARRAY) {
int element_type = dbus_message_iter_get_element_type(&iter);
if (element_type == DBUS_TYPE_STRING) {
DBusMessageIter sub;
dbus_message_iter_recurse(&iter, &sub);
// 先计算数组长度
int count = 0;
DBusMessageIter temp = sub; // 复制迭代器用于计数
while (dbus_message_iter_get_arg_type(&temp) != DBUS_TYPE_INVALID) {
count++;
dbus_message_iter_next(&temp);
}
printf("String array length: %d\n", count);
// 遍历字符串数组
int i = 0;
while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
const char *str;
dbus_message_iter_get_basic(&sub, &str);
printf("Array[%d] = %s\n", i, str);
// 注意:str 指向消息内部,不需要释放
dbus_message_iter_next(&sub);
i++;
}
}
dbus_message_iter_next(&iter);
}
嵌套数组读取:
// 读取数组的数组(如 int32[][])
int type = dbus_message_iter_get_arg_type(&iter);
if (type == DBUS_TYPE_ARRAY) {
int element_type = dbus_message_iter_get_element_type(&iter);
if (element_type == DBUS_TYPE_ARRAY) {
// 外层数组:数组的数组
DBusMessageIter outer;
dbus_message_iter_recurse(&iter, &outer);
int row = 0;
while (dbus_message_iter_get_arg_type(&outer) != DBUS_TYPE_INVALID) {
// 内层数组:整数数组
int inner_element_type = dbus_message_iter_get_element_type(&outer);
if (inner_element_type == DBUS_TYPE_INT32) {
DBusMessageIter inner;
dbus_message_iter_recurse(&outer, &inner);
printf("Row %d: ", row);
while (dbus_message_iter_get_arg_type(&inner) != DBUS_TYPE_INVALID) {
dbus_int32_t value;
dbus_message_iter_get_basic(&inner, &value);
printf("%d ", value);
dbus_message_iter_next(&inner);
}
printf("\n");
}
dbus_message_iter_next(&outer);
row++;
}
}
dbus_message_iter_next(&iter);
}
关键点说明:
- recurse() 创建子迭代器:进入数组时会创建新的迭代器,不影响父迭代器
- 元素类型检查:使用
get_element_type()获取数组元素类型 - 固定数组优化:固定长度类型数组可以使用
get_fixed_array()一次性读取 - 遍历模式:使用
while循环遍历,直到类型为DBUS_TYPE_INVALID - 退出数组:遍历完数组后,必须调用父迭代器的
next()移动到下一个参数
3.2.4 读取结构体
基本结构体读取:
DBusMessageIter iter;
if (!dbus_message_iter_init(message, &iter)) {
return;
}
// 步骤 1: 检查当前参数是否为结构体
int type = dbus_message_iter_get_arg_type(&iter);
if (type == DBUS_TYPE_STRUCT) {
DBusMessageIter sub;
// 步骤 2: 进入结构体(创建子迭代器)
// recurse() 会创建一个新的迭代器,指向结构体的第一个字段
dbus_message_iter_recurse(&iter, &sub);
// 步骤 3: 按顺序读取结构体字段
// 注意:结构体字段必须按顺序读取,不能跳过
// 字段1:字符串(姓名)
int field1_type = dbus_message_iter_get_arg_type(&sub);
if (field1_type == DBUS_TYPE_STRING) {
const char *name;
dbus_message_iter_get_basic(&sub, &name);
printf("Name: %s\n", name);
// 注意:name 指向消息内部,不需要释放
}
dbus_message_iter_next(&sub); // 移动到下一个字段
// 字段2:整数(年龄)
int field2_type = dbus_message_iter_get_arg_type(&sub);
if (field2_type == DBUS_TYPE_INT32) {
dbus_int32_t age;
dbus_message_iter_get_basic(&sub, &age);
printf("Age: %d\n", age);
}
dbus_message_iter_next(&sub); // 移动到下一个字段(如果有)
// 字段3:布尔值(是否激活)
int field3_type = dbus_message_iter_get_arg_type(&sub);
if (field3_type == DBUS_TYPE_BOOLEAN) {
dbus_bool_t active;
dbus_message_iter_get_basic(&sub, &active);
printf("Active: %s\n", active ? "yes" : "no");
}
// 如果还有更多字段,继续读取...
// 步骤 4: 移动到下一个参数(退出结构体)
dbus_message_iter_next(&iter);
}
通用结构体读取函数:
// 辅助函数:读取结构体 (string, int32, boolean)
dbus_bool_t read_person_struct(DBusMessageIter *iter,
const char **name,
int *age,
dbus_bool_t *active)
{
DBusMessageIter sub;
// 检查类型
if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRUCT) {
return FALSE;
}
// 进入结构体
dbus_message_iter_recurse(iter, &sub);
// 读取字段1:姓名
if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING) {
return FALSE;
}
dbus_message_iter_get_basic(&sub, name);
dbus_message_iter_next(&sub);
// 读取字段2:年龄
if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INT32) {
return FALSE;
}
dbus_message_iter_get_basic(&sub, age);
dbus_message_iter_next(&sub);
// 读取字段3:激活状态
if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_BOOLEAN) {
return FALSE;
}
dbus_message_iter_get_basic(&sub, active);
return TRUE;
}
// 使用示例
DBusMessageIter iter;
if (dbus_message_iter_init(message, &iter)) {
const char *name;
int age;
dbus_bool_t active;
if (read_person_struct(&iter, &name, &age, &active)) {
printf("Person: %s, %d years old, %s\n",
name, age, active ? "active" : "inactive");
}
dbus_message_iter_next(&iter);
}
嵌套结构体读取:
// 结构体:(string, (int32, string))
// 表示:姓名、地址(邮编、街道)
int type = dbus_message_iter_get_arg_type(&iter);
if (type == DBUS_TYPE_STRUCT) {
DBusMessageIter outer;
dbus_message_iter_recurse(&iter, &outer);
// 字段1:姓名(字符串)
const char *name;
dbus_message_iter_get_basic(&outer, &name);
printf("Name: %s\n", name);
dbus_message_iter_next(&outer);
// 字段2:地址(嵌套结构体)
int address_type = dbus_message_iter_get_arg_type(&outer);
if (address_type == DBUS_TYPE_STRUCT) {
DBusMessageIter inner;
dbus_message_iter_recurse(&outer, &inner);
// 地址字段1:邮编(整数)
dbus_int32_t zipcode;
dbus_message_iter_get_basic(&inner, &zipcode);
printf("Zipcode: %d\n", zipcode);
dbus_message_iter_next(&inner);
// 地址字段2:街道(字符串)
const char *street;
dbus_message_iter_get_basic(&inner, &street);
printf("Street: %s\n", street);
}
dbus_message_iter_next(&iter);
}
关键点说明:
- 字段顺序:结构体字段必须按顺序读取,不能跳过中间字段
- 类型检查:读取前应该检查字段类型,避免类型不匹配
- 子迭代器:
recurse()创建子迭代器,不影响父迭代器 - 字段遍历:使用
next()移动到下一个字段 - 退出结构体:读取完所有字段后,调用父迭代器的
next()移动到下一个参数 - 嵌套支持:结构体可以嵌套,形成复杂的层次结构
4. 参数类型和签名
4.1 D-Bus 类型系统
D-Bus 支持以下基本类型:
| 类型代码 | 类型名 | C 类型 | 说明 |
|---|---|---|---|
'y' | BYTE | dbus_uint8_t | 8 位无符号整数 |
'b' | BOOLEAN | dbus_bool_t | 布尔值(4 字节) |
'n' | INT16 | dbus_int16_t | 16 位有符号整数 |
'q' | UINT16 | dbus_uint16_t | 16 位无符号整数 |
'i' | INT32 | dbus_int32_t | 32 位有符号整数 |
'u' | UINT32 | dbus_uint32_t | 32 位无符号整数 |
'x' | INT64 | dbus_int64_t | 64 位有符号整数 |
't' | UINT64 | dbus_uint64_t | 64 位无符号整数 |
'd' | DOUBLE | double | 双精度浮点数 |
's' | STRING | const char* | UTF-8 字符串 |
'o' | OBJECT_PATH | const char* | 对象路径 |
'g' | SIGNATURE | const char* | 类型签名 |
'h' | UNIX_FD | int | Unix 文件描述符 |
4.2 容器类型
| 类型代码 | 类型名 | 说明 |
|---|---|---|
'a' | ARRAY | 数组 |
'r' | STRUCT | 结构体(使用 () 表示) |
'e' | DICT_ENTRY | 字典条目(使用 {} 表示) |
'v' | VARIANT | 变体类型 |
4.3 类型签名
类型签名是描述参数类型的字符串:
示例:
"s":单个字符串"is":整数 + 字符串"a{sv}":字典(字符串键,Variant 值)"(is)":结构体(整数 + 字符串)"a(is)":结构体数组
4.4 获取消息签名
const char *signature = dbus_message_get_signature(message);
if (signature != NULL) {
printf("Message signature: %s\n", signature);
}
注意:签名字符串指向消息内部数据,不需要释放。
5. 参数验证
5.1 类型验证
dbus_message_get_args() 会自动验证参数类型:
// 如果类型不匹配,会设置错误
if (!dbus_message_get_args(
msg, &error,
DBUS_TYPE_STRING, &str, // 期望字符串
DBUS_TYPE_INVALID)) {
// 如果消息的第一个参数不是字符串,会失败
fprintf(stderr, "Type mismatch: %s\n", error.message);
}
5.2 数量验证
// 如果参数数量不匹配,迭代器会返回 DBUS_TYPE_INVALID
DBusMessageIter iter;
dbus_message_iter_init(message, &iter);
int count = 0;
while (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_INVALID) {
count++;
dbus_message_iter_next(&iter);
}
printf("Message has %d arguments\n", count);
5.3 字符串验证
添加字符串时会自动验证 UTF-8 编码:
// 无效的 UTF-8 字符串会导致失败
const char *invalid_utf8 = "\xFF\xFE"; // 无效 UTF-8
if (!dbus_message_append_args(
msg,
DBUS_TYPE_STRING, &invalid_utf8,
DBUS_TYPE_INVALID)) {
// 会失败
}
6. 最佳实践
6.1 错误处理
DBusError error;
dbus_error_init(&error);
if (!dbus_message_get_args(msg, &error, ...)) {
fprintf(stderr, "Failed to get args: %s\n", error.message);
dbus_error_free(&error);
return;
}
6.2 参数数量检查
// 检查参数数量
const char *signature = dbus_message_get_signature(msg);
int expected_count = strlen(signature); // 简化示例
DBusMessageIter iter;
dbus_message_iter_init(msg, &iter);
int actual_count = 0;
while (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_INVALID) {
actual_count++;
dbus_message_iter_next(&iter);
}
if (actual_count != expected_count) {
fprintf(stderr, "Argument count mismatch\n");
return;
}
6.3 类型安全
// 使用迭代器时检查类型
int type = dbus_message_iter_get_arg_type(&iter);
if (type != DBUS_TYPE_STRING) {
fprintf(stderr, "Expected string, got type %c\n", type);
return;
}
6.4 内存管理
// 字符串指针指向消息内部,不需要释放
const char *str;
dbus_message_get_args(msg, NULL,
DBUS_TYPE_STRING, &str,
DBUS_TYPE_INVALID);
// str 指向消息内部数据,消息释放后无效
// 数组指针也指向消息内部
const dbus_int32_t *array;
int array_len;
dbus_message_get_args(msg, NULL,
DBUS_TYPE_ARRAY, DBUS_TYPE_INT32, &array, &array_len,
DBUS_TYPE_INVALID);
// array 指向消息内部数据
8449

被折叠的 条评论
为什么被折叠?



