在嵌入式系统中集成 Rust 和 Qt 的实践

出处:维库电子市场网 发布于:2023-02-09 16:13:26

    Rust 为嵌入式软件提供高性能、高可靠性和强大的安全性,并帮助开发人员检查并减少复杂、低级应用程序常见的内存和线程错误。凭借这些明显的优势,Rust 在嵌入式软件开发人员中越来越受欢迎。同样,作为跨平台嵌入式应用程序的框架之一,Qt 为嵌入式领域带来了强大的业务逻辑功能。由于 Rust 还没有的图形用户界面 (GUI),这意味着 Rust-Qt 集成对嵌入式开发人员越来越感兴趣,并已成为向嵌入式 Rust 应用程序添加 GUI 的关键方法之一。
    将两者整合在一起时,挑战就来了。如果没有仔细的计划和努力,组合的 Rust-Qt 应用程序就会成为这两种语言弱属性的牺牲品。Qt/C++对Rust的调用可能不安全,域间可能会出现并发问题,Rust的高性能和可移植性可能会打折扣。
    本文介绍了为嵌入式应用程序实现 Rust 和 Qt 世界的推荐原则。
    Rust 和 Qt 的背景
    Rust 拥有丰富的库生态系统,用于序列化和反序列化、异步操作、解析不安全输入、线程、静态分析等,而 Qt 是一个 C++ 工具包,支持跨各种平台的丰富的、基于 GUI 的应用程序,从 iOS 到嵌入式Linux。Qt 应用程序包括表示业务逻辑的 C++ 插件、定义 GUI 组件和布局的 QML 以及用于 GUI 脚本的 JavaScript。
    将 Rust 库集成到 Qt 框架中,为开发人员提供了一种强大的机制来提供高性能和安全的基础,从而推动复杂的用户体验。考虑一个农业仪表板应用程序,该应用程序需要安全访问后端 I/O 微控制器或从基于 RTOS 的单板计算机提取数据的飞机驾驶舱显示器
    一种推荐的应用程序架构是将业务逻辑保留在 Rust 后端,并为用户界面使用 Qt/C++ 插件。这解耦了两个域,将 Qt/QML 用于 GUI 开发的快速迭代速度和灵活性从业务级代码中分离出来。
    如何为嵌入式软件集成 Rust 和 Qt

    将 Qt 与 Rust 集成的常见方法是让 Rust 调用 Qt 的 C++ 库。虽然有几种绑定方法,但这些方法通常不是 Rust 惯用的,并且往往会失去安全优势,如图 1 所示。此外,Qt 的大多数 Rust 绑定不会将 Rust 代码暴露给 C++,这使得它们难以集成到现有的 C++ 代码库中。

    图 1. Rust-Qt 绑定的挑战()
    一种更有效的方法是以安全的方式连接两种语言,尽可能多地保留 Rust 固有的安全优势。为此,Rust 需要能够使用自己的QObject子类和实例以的妥协来扩展 Qt 对象系统。
    后一种方法的一个例子是开源库CXX-Qt,它由 KDAB 发起并管理其正在进行的开发和改进。该库将 Qt 强大的面向对象和元对象系统与 Rust 相结合,并基于和扩展了另一个开源 Rust/C++ 互操作性库CXX 。在 CXX-Qt 中,新的QObject子类由 Rust 模块中的项目组成,以执行桥接功能。这些子类就像 QML 和 C++ 中的任何其他QObject一样被实例化,将 Rust 特性暴露给 Qt。
    CXX-Qt 定义的每个QObject都包含两个组件:
    一个基于 C++ 的对象,它是一个公开属性和可调用方法的包装器
    存储属性、实现可调用对象、管理内部状态并处理来自属性和后台线程的更改请求的Rust结构
    CXX-Qt 自动生成代码以在 Rust 和 Qt/C++ 域之间传输数据,并使用名为CXX的库在两者之间进行通信。
    CXX-Qt 桥接方法的主要原则
    为了解释有效的 Rust-Qt 桥是如何工作的,我们将描述 CXX-Qt 库背后的几个关键原则。
    在 Rust 中声明 QObject
    Qt 的设计本质上是面向对象的,对于 C++ 和 QML 都是如此,而 Rust 不支持继承和多态性等常见的类特性。为了克服这个限制,CXX-Qt 在 Rust 中扩展了 Qt 对象系统,以提供一种自然的方式来集成两种语言,同时保持惯用的 Rust 代码。
    CXX-Qt 桥可以包括以下部分:
    模块周围的宏指示内容与 CXX-Qt 相关
    定义 QObject、Qt 的任何属性和任何私有状态的结构
    Qobject 结构的可选实现,其中函数可以标记为 qinvokable,允许从 QML 和 C++ 调用它们
    定义 QObject 信号的枚举
    普通 CXX 块
    CXX-Qt 在代码生成期间将此 Rust 模块扩展为QObject的 C++ 子类和RustObj结构,如图 2 所示。
    图 2. CXX-Qt 如何扩展 QObjects 以供运行时使用()
    下面是一个 QObject 示例,其中包含三个用于跨域操作的可调用方法和一个 Rust-only 方法:
    #[cxx_qt::bridge]
    mod my_object {
    不安全的外部“C++”{
    包括!(“cxx-qt-lib/qstring.h”);
    输入 QString = cxx_qt_lib::QString;
    包括!(“cxx-qt-lib/qurl.h”);
    输入 QUrl = cxx_qt_lib::QUrl;
    }
    #[cxx_qt::qobject]
    #[推导(默认)]
    酒吧结构我的对象{
    #[q属性]
    is_connected:布尔值,
    #[q属性]
    网址:QUrl,
    }
    #[cxx_qt::qsignals(MyObject)]
    酒吧枚举连接{
    连接的,
    错误{消息:QString},
    }
    实现 qobject::MyObject {
    #[qinvokable]
    pub fn connect(mut self: Pin<&mut Self>, url: QUrl) {
    self.as_mut().set_url(url);
    如果自己
    .as_ref()
    .url()
    .to_string()
    .starts_with("https://kdab.com")
    {
    self.as_mut().set_is_connected(true);
    self.emit(Connection::Connected);
    } 别的 {
    self.as_mut().set_is_connected(false);
    self.emit(连接::错误{
    message: QString::from("URL 不以 https://kdab.com" 开头),
    });
    }
    }
    }
    }
    使用#[qinvokable]属性声明的方法(例如上面的connect)暴露给 QML 和 C++,参数和返回类型在 Qt 端匹配。使用#[qproperty] 属性(例如上面的 is_connected)声明的 QObject 结构中的字段作为 Q_PROPERTY 公开给 QML 和 C++。用#[cxx_qt::qsignals(T)] 属性标记的枚举定义了在 QObject T 上声明的信号。请注意,CXX-Qt 会自动在 snake_case (Rust) 和 CamelCase (Qt) 命名之间进行转换。
    在此示例中,connect 方法用于从 Rust 改变 QObject 的 url 和 connected 属性。然后发出一个信号,指示连接是否成功或是否发生错误。
    没有用属性标记的方法或字段被认为是 Rust 私有的,可用于管理内部状态或用作线程实例数据。
    由于QObject是桥的 C++ 端拥有的构造 Qt 对象,因此它会在运行时销毁 C++ 对象时被销毁。
    跨桥声明通用数据类型
    原始数据类型和 CXX 类型可以跨桥使用,不需要在 Rust 和 Qt 之间进行转换。库 cxx_qt_lib 提供代表常见 Qt 类型(例如 QColor、QString、QVariant 等)的 Rust 类型,以供跨桥使用。
    随着项目的发展,我们计划在cxx_qt_lib中加入更多常用的Qt类型,比如Qt容器类型(如QHash和 QVector) 和其他对 Qt C++ 或 QML 有用的类型。然后,我们希望通过向 Rust 生态系统中已建立的板条箱添加转换来增强这些类型。例如,我们计划支持从 QColor 到颜色包类型的转换或从 QDateTime 到日期时间包类型的转换,或者使用 Rust 包对 Qt 类型进行(反)序列化等。
    保持线程安全
    Rust-Qt 应用程序中安全线程背后的一般概念是每当 Rust 代码执行时在 C++ 端获取一个锁,以防止该代码被多个线程执行。这意味着所有直接从 C++ 调用的 Rust 代码,例如可调用对象,仅在 Qt 线程上执行。
    为了允许开发人员将状态从后台 Rust 线??程同步到 Qt,CXX-Qt 提供了一个帮助程序,允许您从后台线程对 Rust 闭包进行排队。然后从 Qt 事件循环中执行闭包。
    // 在可调用对象中,请求 qt 线程的句柄
    让 qt_thread = self.qt_thread();
    // 产生一个 Rust 线??程
    std::thread::spawn(移动 || {
    让 value = compute_value_on_rust_thread();
    // 使用闭包移动值并在 Qt 事件循环中运行任务
    线程
    .queue(移动|mut qobject| {
    // 发生在 Qt 事件循环中
    qobject.set_value(值);
    })
    .unwrap();
    });
    将 Rust QObject 暴露给 QML
    要在 Qt 应用程序中使用 Rust 代码,必须将 Rust QObject 导出到 QML。CXX-Qt 通过为Rust 中定义的每个QObject子类生成一个 C++ 类来简化这个过程——开发人员只需要在 main.cpp 中包含两个语句:
    包括 QObject 类,例如,
    #include "cxx-qt-gen/my_object.h"
    将 QObject 类导出到 QML,例如,
    qml注册类型( "com.kdab.cxx_qt.demo" , 1 , 0 , "MyObject" );
    构建系统
    CXX-Qt 支持使用 CMake 或 Cargo 构建。
    使用 CMake,Corrosion(以前称为 cmake-cargo)用于将 Rust crate 作为静态库导入,然后链接到您的 Qt 应用程序可执行文件或库。这允许将 CXX-Qt 轻松集成到现有的 CMake Qt 应用程序中。构建 Rust crate 时,build.rs 文件会编译 CXX-Qt 生成的任何 C++ 代码。
    对于使用 Rust 的 Cargo 构建系统的项目,CXX-Qt 也可以在没有 CMake 的情况下从 Cargo 使用。使用此方法,Rust 项目的 build.rs 文件还会触发 Qt 资源的构建、任何其他 C++ 文件的编译以及链接到任何其他 Qt 模块。
    结论
    随着集成 Rust 和 Qt 的用例的增长,开发团队应该知道他们除了一对一直接绑定之外的选择。CXX-Qt 库提供了一种可行的桥接机制,以惯用的方式保留了 Rust 的线程安全和性能优势,同时还允许开发人员使用普通的 Qt 和 Rust 代码。
    通过理解这里的概念,包括 Qt 对象和 Rust QObject之间的映射、Rust 的常见 Qt 类型以及定义运行时互操作性的宏和代码生成,开发人员可以更好地为他们的应用程序开发选择正确的 Rust-Qt 集成机制。
关键词:嵌入式系统

版权与免责声明

凡本网注明“出处:维库电子市场网”的所有作品,版权均属于维库电子市场网,转载请必须注明维库电子市场网,https://www.dzsc.com,违反者本网将追究相关法律责任。

本网转载并注明自其它出处的作品,目的在于传递更多信息,并不代表本网赞同其观点或证实其内容的真实性,不承担此类作品侵权行为的直接责任及连带责任。其他媒体、网站或个人从本网转载时,必须保留本网注明的作品出处,并自负版权等法律责任。

如涉及作品内容、版权等问题,请在作品发表之日起一周内与本网联系,否则视为放弃相关权利。

上传BOM文件: BOM文件
*公司名:
*联系人:
*手机号码:
QQ:
应用领域:

有效期:
OEM清单文件: OEM清单文件
*公司名:
*联系人:
*手机号码:
QQ:
有效期:

扫码下载APP,
一键连接广大的电子世界。

在线人工客服

买家服务:
卖家服务:

0571-85317607

客服在线时间周一至周五
9:00-17:30

关注官方微信号,
第一时间获取资讯。

建议反馈

联系人:

联系方式:

按住滑块,拖拽到最右边
>>
感谢您向阿库提出的宝贵意见,您的参与是维库提升服务的动力!意见一经采纳,将有感恩红包奉上哦!