嵌入式技术
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 集成机制。
全部0条评论
快来发表一下你的评论吧 !