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

Download history 230/week @ 2024-04-20 245/week @ 2024-04-27 227/week @ 2024-05-04 281/week @ 2024-05-11 227/week @ 2024-05-18 250/week @ 2024-05-25 241/week @ 2024-06-01 217/week @ 2024-06-08 238/week @ 2024-06-15 334/week @ 2024-06-22 55/week @ 2024-06-29 125/week @ 2024-07-06 301/week @ 2024-07-13 227/week @ 2024-07-20 221/week @ 2024-07-27 252/week @ 2024-08-03

1,007 每月下载量
207 个 crates 中使用 (9 直接)

MIT 许可协议

380KB
7K SLoC

Rust 的 QMetaObject crate

Crates.io Documentation

一个框架,让每个人都能用 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

启用 QDateQTime 与 Rust chrono 包的互操作性。

此功能默认禁用。

webengine

启用 QtWebEngine 功能。更多详细信息请参阅 示例

此功能默认禁用。

如果缺少 Qt C++ API 的包装器怎么办?

你可能想调用此 crate 没有包装的特定 Qt 函数。

在这种情况下,你可以始终使用 cpp! 宏直接从你的 Rust 代码中访问 C++。

我们努力增加包装 API 的覆盖率,所以每当有你需要但当前缺失的东西时,欢迎你在 GitHub 问题上提出功能请求或立即发送 Pull Request。

教程:为 Qt C++ API 添加 Rust 包装器

本节介绍如何创建自己的 crate 并添加新的 Qt 包装器,并逐步通过本存储库提供的 Graph 示例。

首先,设置你的 Cargo.tomlbuild.rs

  1. 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"
  2. cpp 添加到依赖项,并将 cpp_build 添加到构建依赖项。您可以在 cpp 文档 页面上找到最新的说明。

    [dependencies]
    cpp = "0.5"
    
    [build-dependencies]
    cpp_build = "0.5"
    
  3. 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 不同,它使用自己的语言。
    Slint Logo

qmetaobject crate 目前仅被被动维护,因为重点已经转移到开发 Slint。我们鼓励您探索 Slint 作为创建 Rust 项目 GUI 的激动人心、创新的替代方案。

使用此crate构建的应用程序

依赖关系

~1–2.3MB
~42K SLoC