18 个版本
0.2.10 | 2023年11月3日 |
---|---|
0.2.9 | 2023年6月17日 |
0.2.7 | 2022年2月23日 |
0.2.5 | 2021年11月19日 |
0.0.1 | 2018年7月9日 |
#27 in GUI
1,007 每月下载量
在 207 个 crates 中使用 (9 直接)
380KB
7K SLoC
Rust 的 QMetaObject crate
一个框架,让每个人都能用 Rust 创建 Qt/QML 应用程序。它通过在编译时构建 QMetaObject
、注册 QML 类型(可选通过暴露 QQmlExtensionPlugin
)和提供惯用包装来实现。
目标
- Rust 过程宏(自定义 derive)在编译时生成
QMetaObject
。 - 使用
cpp
crate 的cpp!
宏为主 Qt 类型提供绑定。 - 使用此 crate 的用户不需要输入任何 C++ 代码或使用除 cargo 之外的任何构建系统。
- 性能:避免任何不必要的转换或堆分配。
概述博客文章: https://woboq.com/blog/qmetaobject-from-rust.html
概述
use cstr::cstr;
use qmetaobject::prelude::*;
// The `QObject` custom derive macro allows to expose a class to Qt and QML
#[derive(QObject, Default)]
struct Greeter {
// Specify the base class with the qt_base_class macro
base: qt_base_class!(trait QObject),
// Declare `name` as a property usable from Qt
name: qt_property!(QString; NOTIFY name_changed),
// Declare a signal
name_changed: qt_signal!(),
// And even a slot
compute_greetings: qt_method!(fn compute_greetings(&self, verb: String) -> QString {
format!("{} {}", verb, self.name.to_string()).into()
})
}
fn main() {
// Register the `Greeter` struct to QML
qml_register_type::<Greeter>(cstr!("Greeter"), 1, 0, cstr!("Greeter"));
// Create a QML engine from rust
let mut engine = QmlEngine::new();
// (Here the QML code is inline, but one can also load from a file)
engine.load_data(r#"
import QtQuick 2.6
import QtQuick.Window 2.0
// Import our Rust classes
import Greeter 1.0
Window {
visible: true
// Instantiate the rust struct
Greeter {
id: greeter;
// Set a property
name: "World"
}
Text {
anchors.centerIn: parent
// Call a method
text: greeter.compute_greetings("hello")
}
}
"#.into());
engine.exec();
}
特性
- 创建继承自 QObject、QQuickItem、QAbstractListModel、QQmlExtensionPlugin、... 的对象
- 导出 Qt 属性、信号、方法、...
- 也支持
#[derive)]
(与 Q_GADGET 相同) - 创建 Qt 插件(参见 examples/qmlextensionplugins)
- 部分场景图支持
需要 Qt >= 5.8
Cargo 功能
Cargo 提供了一种启用(或禁用默认)可选 功能 的方法。
log
默认情况下,Qt 的日志系统未初始化,例如来自 QML 的 console.log
的消息不会传到任何地方。 "log" 功能使 log
crate(Rust 日志外观)集成成为可能。
该功能默认启用。要激活它,尽可能早地在 main()
中执行以下代码。
fn main() {
qmetaobject::log::init_qt_to_rust();
// don't forget to set up env_logger or any other logging backend.
}
chrono_qdatetime
启用 QDate
和 QTime
与 Rust chrono
包的互操作性。
此功能默认禁用。
webengine
启用 QtWebEngine
功能。更多详细信息请参阅 示例。
此功能默认禁用。
如果缺少 Qt C++ API 的包装器怎么办?
你可能想调用此 crate 没有包装的特定 Qt 函数。
在这种情况下,你可以始终使用 cpp!
宏直接从你的 Rust 代码中访问 C++。
我们努力增加包装 API 的覆盖率,所以每当有你需要但当前缺失的东西时,欢迎你在 GitHub 问题上提出功能请求或立即发送 Pull Request。
教程:为 Qt C++ API 添加 Rust 包装器
本节介绍如何创建自己的 crate 并添加新的 Qt 包装器,并逐步通过本存储库提供的 Graph 示例。
首先,设置你的 Cargo.toml 和 build.rs
-
将
qttypes
添加到依赖项。你可能只会坚持使用在 crates.io 上发布的最新版本。[dependencies] qttypes = { version = "0.2", features = [ "qtquick" ] }
将你需要的更多 Qt 模块添加到功能数组中。有关支持的模块完整列表,请参阅 qttypes crate 文档。
如果你 绝对需要 最新未发布的更改,则使用此替代version = "..."
path = "../path/to/qmetaobject-rs/qttypes"
或git= "https://github.com/woboq/qmetaobject-rs"
-
将
cpp
添加到依赖项,并将cpp_build
添加到构建依赖项。您可以在cpp
文档 页面上找到最新的说明。[dependencies] cpp = "0.5" [build-dependencies] cpp_build = "0.5"
-
从 qmetaobject/build.rs 复制 build.rs 脚本。它将运行
cpp_build
与你的包进行交互,使用 qttypes/build.rs 提供的环境。
现在,每次你构建你的包时,cpp!
宏的内容都将收集到一个大 C++ 文件中,并编译成静态库,稍后将链接到最终二进制文件中。您可以在 Cargo 目标目录中找到此 cpp_closures.cpp 文件。了解其内容可能有助于故障排除。
cpp!
宏有两种形式。
-
具有双大括号
{{
的形式将其实际内容附加到 C++ 文件中。用于#include
头文件,定义 C++ 结构体和类等。 -
另一种用法是在运行时调用表达式。通常使用
(
括号)
,它接受[
参数]
列表,并需要一个unsafe
标记(可以是周围块或作为第一个关键词内部)。
宏调用的顺序在每个文件(Rust模块)的基础上是保持不变的;但文件的处理顺序并不保证与 mod
声明的顺序一致。因此,不要假设可见性——确保在每个Rust模块的顶部都包含所需的一切。
查看 关于 cpp
的文档 了解其内部工作原理。
现在我们一切都准备好了,让我们来看看Graph示例的代码。它位于 examples/graph 目录。
在添加包装器之前,我们将相关的 #include
行放在一个 {{
双括号 }}
宏中。
cpp! {{
#include <QtQuick/QQuickItem>
}}
如果您需要包含自己的本地C++头文件,也可以这样做!查看main qmetaobject crate是如何在每个需要它的Rust模块中包含 qmetaobject_rust.hpp 头文件的。
接下来,我们声明一个自定义的QObject,就像在 概览 中一样,但这次它继承自 QQuickItem
。尽管它的名字,#[derive(QObject)]
proc-macro 可以与多个基类一起工作,只要它被正确封装并实现了 QObject
特性。
#[derive(Default, QObject)]
struct Graph {
base: qt_base_class!(trait QQuickItem),
// ...
}
我们希望调用 QQuickItem::setFlag
方法,但目前它还没有在 qmetaobject-rs API 中公开,所以我们将直接调用它。
impl Graph {
fn appendSample(&mut self, value: f64) {
// ...
let obj = self.get_cpp_object();
cpp!(unsafe [obj as "QQuickItem *"] {
obj->setFlag(QQuickItem::ItemHasContents);
});
// ...
}
}
或者,我们可以添加一个适当的方法包装器,并使用它而不需要 unsafe
#[repr(C)]
enum QQuickItemFlag {
ItemClipsChildrenToShape = 0x01,
ItemAcceptsInputMethod = 0x02,
ItemIsFocusScope = 0x04,
ItemHasContents = 0x08,
ItemAcceptsDrops = 0x10,
}
impl Graph {
fn set_flag(&mut self, flag: QQuickItemFlag) {
let obj = self.get_cpp_object();
assert!(!obj.is_null());
cpp!(unsafe [obj as "QQuickItem *", flag as "QQuickItem::Flag"] {
obj->setFlag(flag);
});
}
fn appendSample(&mut self, value: f64) {
// ...
self.set_flag(QQuickItemFlag::ItemHasContents);
// ...
}
}
请注意,C++方法接受可选的第二个参数,但由于Rust和FFI胶水不支持可选参数,它总是被省略(默认为 true
)。为了改进这种情况,我们可以在Rust函数中添加第二个必需参数,或者实现两个具有略微不同名称的“重载”,例如 set_flag(Flag, bool)
& set_flag_on(Flag)
或 enable_flag(Flag)
等。
如果对象在使用前保证正确实例化和初始化,则不需要对非空进行断言。这适用于以下情况
-
直接调用
QObject::cpp_construct()
并将其结果存储在不可移动的内存位置。 -
构建
QObjectPinned
实例:任何对固定对象或转换为QVariant
的访问都确保创建 C++ 对象; -
将对象作为 QML 组件实例化。在设置任何属性或调用任何信号/槽之前,QML 引擎总是正确地默认初始化它们。
就这样!您已经实现了一个 Qt C++ 类方法的新的包装器。现在给我们发送一个 Pull Request。😄
与其他项目的比较
这个crate的主要目标是提供 QML 的惯用 Rust 绑定。它仅关注 QML,不关注 QWidgets 或任何其他非图形 Qt API。目标是消除用户需要了解或使用 C++ 和其他构建系统的需求。这个crate在实现这个目标方面表现卓越。
-
CXX-Qt 是将一些 Rust 集成到现有 C++ 项目中的理想解决方案。CXX-Qt 比这个crate更新,并利用了 Rust 功能,如属性宏,这在 qmetaobject crate 设计时还不存在。(当时稳定 Rust 中只有过程宏可用)
-
Rust Qt Binding Generator 是另一个项目,有助于将 Rust 逻辑集成到现有的 C++/Qt 项目中。这个项目也是在 Rust 有过程宏之前开发的,因此它使用外部 .json 文件生成 C++ 和 Rust 代码。
-
还有一些旧的crate尝试为 Qt C++ API 提供 Rust 绑定。这些绑定通常是自动生成的,不是惯用 Rust,使用时需要 unsafe 代码,并且不再维护。
-
Slint 是由这个crate的作者创建的项目。Slint 不是一个 QML 或 Qt 绑定,而是一个从 QML 启发的新语言,完全使用 Rust 实现。它与提供一种方法来为 Rust 项目添加 UI 的目标相同,但与使用 QML 进行 UI 不同,它使用自己的语言。
qmetaobject crate 目前仅被被动维护,因为重点已经转移到开发 Slint。我们鼓励您探索 Slint 作为创建 Rust 项目 GUI 的激动人心、创新的替代方案。
使用此crate构建的应用程序
依赖关系
~1–2.3MB
~42K SLoC