工作需要用c接口调用dbus,在这里写篇博客记录一下。
1. 方案比较
用C接口调用dbus一般来说有3种方案,分别是libdbus、GDBus(GIO的一部分)和sd-bus(systemd的一部分),以下比较了3种方案的优劣:
接口方案 | libdbus | GDBus | sd-bus |
api类型 | 低层 C API | GLib 的高层封装 | 系统级 C API |
依赖关系 | 需要dbus | 需要glib | 需要systemd |
易用性 | 复杂 | 简单 | 较简单 |
性能 | 好 | 有少量开销 | 好 |
之前一直用的libdbus,不过太复杂了,维护成本高。
这次是在linux上使用,系统自带systemd,因此这次选择了sd-bus,简单且不需要额外的依赖。
2. sd-bus的使用
2.1 安装
sudo apt update
sudo apt install libsystemd-dev
2.2 Demo
// sd-bus的头文件
#include <systemd/sd-bus.h>
#include <stdio.h>int main(int argc, char *argv[]) {sd_bus *bus = NULL;sd_bus_message *msg = NULL;sd_bus_error error = SD_BUS_ERROR_NULL;int ret;// 连接到system bus用sd_bus_open_system// session bus的话用sd_bus_open_userret = sd_bus_open_system(&bus);if (ret < 0) {fprintf(stderr, "Failed to connect to system bus: %s\n", strerror(-ret));return ret;}// 调用Reboot方法ret = sd_bus_call_method(bus,"org.freedesktop.login1", // Service to contact"/org/freedesktop/login1", // Object path"org.freedesktop.login1.Manager", // Interface name"Reboot", // Method to call&error, // No reply expected&msg, // Output messageNULL); // No input argumentsif (ret < 0) {fprintf(stderr, "Failed to call method: %s\n", strerror(-ret));sd_bus_unref(bus);return ret;}// 检查返回值int response = 0;// 假如返回值是int类型ret = sd_bus_message_read(msg, "i", &response); if (ret < 0) {fprintf(stderr, "Failed to read response: %s\n", strerror(-ret));} else {printf("Method returned response: %d\n", response);}// Cleanupsd_bus_message_unref(msg);sd_bus_unref(bus);return 0;
}
简单介绍一下,使用sd-bus的核心接口只有3个,连接到总线,调用method,以及读取返回值
2.2.1 连接到总线
连接到总线的接口有3个,函数原型如下:
int sd_bus_open_system(sd_bus **ret);
// 连接system bus
int sd_bus_open_user(sd_bus **ret);
// 连接session bus
int sd_bus_default(sd_bus **ret);
// 先连接session bus,不可用再连接system bus
2.2.2 调用method
函数原型如下:
// sd_bus_call_method 函数原型
int sd_bus_call_method(sd_bus *bus, // 总线连接const char *destination, // 目标服务名const char *path, // 对象路径const char *interface, // 接口名称const char *member, // 方法名sd_bus_error *ret_error, // 错误信息sd_bus_message **reply, // 响应消息const char *types, // 参数类型字符串... // 可变参数列表
);
调用带多个参数的method
// 调用带多个参数的method
ret = sd_bus_call_method(bus,"org.example.Service","/org/example/object","org.example.Interface","ComplexMethod",&error,&reply,"siay", // string, int32, array of bytes"test", // string参数42, // int32参数3, // 数组长度(uint8_t[]){1,2,3}); // byte数组/* 常用参数类型说明:* s: string (const char *)* u: uint32_t* i: int32_t* x: int64_t* t: uint64_t* b: boolean (int)* y: byte (uint8_t)* d: double* ay: array of bytes* as: array of strings* a{sv}: dictionary of string keys to variant values* v: variant* o: object path*/
调用带一个参数的method
// 调用带参数的方法ret = sd_bus_call_method(bus,"org.freedesktop.systemd1", // 服务"/org/freedesktop/systemd1", // 路径"org.freedesktop.systemd1.Manager", // 接口"GetUnit", // 方法&error, // 错误对象&reply, // 响应消息"s", // 参数类型: 字符串"sshd.service"); // 参数值
2.2.3 读取返回值
函数原型如下,第一个入参是调用method,引用传递返回的reply。
// sd_bus_message_read 函数原型
int sd_bus_message_read(sd_bus_message *m, // 消息对象const char *types, // 数据类型字符串... // 要读取的变量指针
);
读取多个返回值的示例如下:
int ret;
const char *str;
int32_t num;
uint8_t byte;
double dbl;// 读取基本类型:字符串、32位整数、字节、双精度浮点数
ret = sd_bus_message_read(m, "siyd",&str, // 字符串&num, // 32位整数&byte, // 字节&dbl); // 双精度浮点数