【Linux IPC の d-bus】gdbus-codegen 使用教程

参考文章:https://blog.csdn.net/qq_40650634/article/details/149168037

 

基本概念与编程教程参考一下文章

1.Gdbus 的编程教程:https://blog.csdn.net/adlindary/article/details/80167840

2.DBUS 基础概念:https://blog.csdn.net/yishuige/article/details/52852531

3.DBUS 性能优化-数据的序列化:https://blog.csdn.net/coroutines/article/details/38496145

使用 gdbu-codegen 的好处

使用 gdbus-codegen 工具根据定义的接口 XML 文件生成 .c 和 .h 文件,是利用 GDBus 进行进程间通信(IPC)编程的一种常见做法。这种做法有以下几个主要好处:

1. 简化编码工作

手动编写 D-Bus 方法调用、信号处理等代码不仅繁琐,而且容易出错。通过 gdbus-codegen 自动生成这些代码,可以显著减少开发者的负担,让他们能够专注于业务逻辑的实现而非底层通信细节。

2. 提高代码的一致性和准确性

自动生成的代码遵循 D-Bus 规范和最佳实践,这有助于确保不同模块之间的交互是一致且准确的。开发者不需要担心因为手写的错误而导致的协议不匹配问题。

3. 增强可维护性

当需要修改或扩展 D-Bus 接口时,只需更新 XML 接口描述文件,然后重新运行 gdbus-codegen 即可轻松地生成新的 C 源文件和头文件。这种方式使得代码更加易于维护,减少了由于手动修改带来的风险。

4. 促进模块化设计

将接口定义从具体的实现中分离出来,有利于构建松耦合的系统架构。服务端和客户端都可以依赖于相同的接口定义,这样即使两端的具体实现发生变化,只要接口不变,双方仍然可以无缝协作。

5. 支持异步操作

GDBus 支持异步方法调用和信号发送,而 gdbus-codegen 生成的代码通常会包含对这些特性的支持,使开发者能够更容易地编写高效、响应迅速的应用程序。

6. 便于团队协作

对于大型项目或者多个开发者共同参与的项目来说,有一个清晰的接口定义可以让团队成员更清楚地了解系统的各个部分是如何交互的,从而提高团队的工作效率。

实践中的应用

假设我们要开发一个服务端和客户端应用,其中服务端提供一个方法用于加法运算,并能通过信号通知客户端当前的服务状态。

示例场景

  • 服务端:提供一个名为 AddNumbers 的方法,接收两个整数作为输入参数,返回它们的和。
  • 客户端:调用服务端的 AddNumbers 方法,并监听服务端发送的状态更新信号。

步骤 1: 定义 D-Bus 接口 XML 文件

首先,我们需要定义一个接口描述文件(例如 example.xml),用于描述服务的方法和信号:

<?xml version="1.0" encoding="UTF-8"?>
<node name="/com/example/calculator">
  <interface name="com.example.Calculator">
    <!-- 提供加法运算 -->
    <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>
    
    <!-- 服务状态更新信号 -->
    <signal name="StatusUpdate">
      <arg type="s" name="message"/>
    </signal>
  </interface>
</node>

步骤 2: 使用 gdbus-codegen 生成代码

接下来,利用 gdbus-codegen 根据上述 XML 文件生成 C 语言的代码框架:

gdbus-codegen --generate-c-code=calculator example.xml

这将生成 calculator.c 和 calculator.h 文件,这些文件包含了与 D-Bus 接口交互所需的所有函数原型和数据结构定义。

步骤 3: 实现服务端逻辑

在服务端实现中,我们将导入生成的头文件,并编写处理 AddNumbers 方法和发送 StatusUpdate 信号的具体逻辑:

#include "calculator.h"
#include <gio/gio.h>

// 处理 AddNumbers 方法的回调函数
static void handle_add_numbers(ComExampleCalculator *object, GDBusMethodInvocation *invocation, gint num1, gint num2) {
    gint sum = num1 + num2;
    g_print("Adding %d and %d results in %d\n", num1, num2, sum);
    
    // 完成方法调用并返回结果
    com_example_calculator_complete_add_numbers(object, invocation, sum);

    // 发送状态更新信号给所有监听的客户端
    com_example_calculator_emit_status_update(object, "Calculation completed.");
}

int main() {
    GMainLoop *loop = g_main_loop_new(NULL, FALSE);
    GError *error = NULL;

    // 创建服务对象实例
    ComExampleCalculator *service = com_example_calculator_skeleton_new();
    
    // 连接 handle_add_numbers 函数到 AddNumbers 方法
    g_signal_connect(service, "handle-add-numbers", G_CALLBACK(handle_add_numbers), NULL);

    // 获取会话总线连接
    GDBusConnection *connection = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, &error);
    if (connection == NULL) {
        g_printerr("Failed to connect to the bus: %s\n", error->message);
        return 1;
    }

    // 将服务对象导出到 D-Bus 上
    g_dbus_interface_skeleton_export(G_DBUS_INTERFACE_SKELETON(service), connection, "/com/example/calculator", &error);
    if (error != NULL) {
        g_printerr("Failed to export object: %s\n", error->message);
        return 1;
    }

    g_print("Service is running...\n");
    
    // 启动主事件循环
    g_main_loop_run(loop);
    return 0;
}
  • handle_add_numbers:这是处理客户端调用 AddNumbers 方法的回调函数。它计算两个数字的和,并通过 com_example_calculator_complete_add_numbers() 返回结果。同时,它还会发送一个 StatusUpdate 信号告知客户端操作已完成。
  • main 函数:
    创建了一个 GMainLoop 实例,用于运行主事件循环。
    使用 com_example_calculator_skeleton_new() 创建了一个新的服务骨架对象。
    使用 g_signal_connect() 将 handle_add_numbers 函数与 AddNumbers 方法绑定。
    使用 g_bus_get_sync() 获取会话总线(session bus)连接。
    最后,使用 g_dbus_interface_skeleton_export() 将服务对象导出到指定的路径 /com/example/calculator 上。

步骤 4: 实现客户端逻辑

客户端需要连接到服务端,调用 AddNumbers 方法,并监听 StatusUpdate 信号:

#include "calculator.h"
#include <gio/gio.h>

// 状态更新信号的处理函数
static void on_status_update(ComExampleCalculator *proxy, const gchar *message, gpointer user_data) {
    g_print("Received status update: %s\n", message);
}

int main() {
    GError *error = NULL;
    
    // 获取会话总线连接
    GDBusConnection *connection = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, &error);
    if (connection == NULL) {
        g_printerr("Failed to connect to the bus: %s\n", error->message);
        return 1;
    }

    // 创建代理对象
    ComExampleCalculator *proxy = com_example_calculator_proxy_new_sync(connection, G_DBUS_PROXY_FLAGS_NONE, "com.example.Calculator", "/com/example/calculator", NULL, &error);
    if (proxy == NULL) {
        g_printerr("Failed to create proxy: %s\n", error->message);
        return 1;
    }

    // 连接信号处理函数
    g_signal_connect(proxy, "status-update", G_CALLBACK(on_status_update), NULL);

    // 调用远程方法
    gint result;
    gboolean success = com_example_calculator_call_add_numbers_sync(proxy, 5, 7, &result, NULL, &error);
    if (!success) {
        g_printerr("Failed to call AddNumbers: %s\n", error->message);
        return 1;
    }
    g_print("Result of AddNumbers: %d\n", result);

    return 0;
}
  • on_status_update:这是一个信号处理函数,每当服务端发出 StatusUpdate 信号时,该函数会被调用,并打印接收到的消息。
  • main 函数:
    首先获取会话总线连接。
    使用 com_example_calculator_proxy_new_sync() 创建一个代理对象,这个对象允许客户端像调用本地方法一样调用远程服务的方法。
    使用 g_signal_connect() 将 on_status_update 函数与 StatusUpdate 信号绑定。
    调用 com_example_calculator_call_add_numbers_sync() 来同步调用服务端的 AddNumbers 方法,并打印返回的结果。

总结

通过这个简单的例子,我们可以看到:

  • 简化编码工作:手动编写 D-Bus 方法调用和信号处理非常复杂,而 gdbus-codegen 自动生成了必要的函数原型和数据结构。
  • 提高代码的一致性和准确性:自动生成的代码遵循标准规范,减少了人为错误。
  • 增强可维护性:如果需要修改或扩展接口,只需更新 XML 文件并重新生成代码即可。
  • 支持异步操作:虽然本例中使用的是同步调用,但生成的代码也支持异步操作,便于构建高效的应用程序。

 

扩展:gdbus-codegen 中 type = ‘s' 是什么意思

在 gdbus-codegen(D-Bus 绑定生成器)中,type = 's' 表示一个 字符串(string) 类型的 D-Bus 参数。在 XML 接口定义中用于标注参数或返回值的类型。

其他常见 D-Bus 类型代码

<arg name="int_param" type="i"/>     <!-- 32位整数 -->
<arg name="bool_param" type="b"/>    <!-- 布尔值 -->
<arg name="double_param" type="d"/>  <!-- 双精度浮点数 -->
<arg name="array_param" type="as"/>  <!-- 字符串数组 -->
<arg name="dict_param" type="a{sv}"/> <!-- 字典 -->

 

使用示例

<interface name="com.example.MyService">
  <!-- 方法参数中使用 -->
  <method name="EchoString">
    <arg name="input" type="s" direction="in"/>
    <arg name="output" type="s" direction="out"/>
  </method>
  
  <!-- 信号中使用 -->
  <signal name="MessageReceived">
    <arg name="message" type="s"/>
  </signal>
  
  <!-- 属性中使用 -->
  <property name="Name" type="s" access="readwrite"/>
</interface>

 

 

 

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