当前位置: 首页 > news >正文

【CXX-Qt】4.1 extern “RustQt“

  • QObjects

  • Properties

+Methods

  • Signals
#[cxx_qt::bridge]
mod ffi {
    extern "RustQt" {
    }
}

extern “RustQt” 部分是 CXX-Qt 桥接的核心,用于声明 Rust 类型和签名,使其可用于 Qt 和 C++。

CXX-Qt 代码生成器使用你的 extern “RustQt” 部分生成一个包含相应 C++ 声明的 C++ 头文件。生成的头文件与输入的 Rust 文件同名,但扩展名为 .cxxqt.h。

一个桥接模块可以包含零个或多个 extern “RustQt” 块。

这补充了 extern “Rust” CXX 部分,但允许在 C++ 类型上声明 Qt 特定的功能。

可以通过块级属性自动转换为驼峰命名或蛇形命名。

QObjects

#[qobject] 属性可以放在类型别名上,以在 C++ 中生成一个 QObject 类型。

类型别名的左侧指定在 C++ 中生成的 QObject 类型。在引用 C++ 上下文时,应使用此类型。类型别名的右侧指定提供类型内部实现的 Rust 类型(例如字段)。

#[cxx_qt::bridge]
mod ffi {
    extern "RustQt" {
        #[qobject]
        type MyObject = super::MyObjectRust;
    }
}

#[derive(Default)]
struct MyObjectRust;
📝 注意:目前,只有 `super::` 允许作为内部 Rust 类型的路径。因此,Rust 类型必须在桥接模块外部可用。如果你想重用现有类型,可以使用 `pub use` 指令将任何类型引入作用域。

QML 属性

通过使用 #[qml_element] 属性,可以在构建时直接将 QObject 注册为 QML 类型。

unsafe extern "RustQt" {
    // QObject 定义
    // 我们告诉 CXX-Qt 我们想要一个名为 MyObject 的 QObject 类
    // 基于 Rust 结构体 MyObjectRust。
    #[qobject]
    #[qml_element]
    #[qproperty(i32, number)]
    #[qproperty(QString, string)]
    #[namespace = "my_object"]
    type MyObject = super::MyObjectRust;
}

此外,你可以使用以下属性配置 QML 注册:

  • #[qml_element]:将类型声明为 QML 元素。可以使用替代类型名称,例如 #[qml_element = “MyName”]。

  • #[qml_uncreatable]:标记该类型无法从 QML 创建。它仍然可以通过 C++/Rust 代码返回。

  • #[qml_singleton]:QObject 的实例将作为单例在 QML 中实例化。

Rust 文件必须包含在 build.rs 文件中的 QML 模块中。

base 属性

使用 base 属性指定 C++ 类,C++ QObject 将从该类继承。基类必须直接或间接继承自 QObject。如果未指定 base 属性,它将直接继承自 QObject。

extern "RustQt" {
    #[qobject]
    #[base = "QAbstractListModel"]
    #[qml_element]
    #[qproperty(State, state)]
    type CustomBaseClass = super::CustomBaseClassRust;
}

使用 CXX 的 include! 宏包含基类的适当 C++ 头文件:

unsafe extern "C++" {
    include!(<QtCore/QAbstractListModel>);
    /// Qt 类型的基类
    type QAbstractListModel;
}

有关继承和如何重写方法的更多信息,请参阅 继承与重写 页面。

完整示例

Traits
#[qobject] 标记的结构体需要实现 Default trait,可以通过手动实现或使用 #[derive(Default)] 宏。或者,类型需要实现 cxx_qt::Constructor trait。

有关更多文档,请参阅 traits 页面。

属性

可以在 #[qobject] 标记的类型上指定 #[qproperty(TYPE, NAME, …)] 属性,以在生成的 QObject 上暴露一个 Q_PROPERTY。

unsafe extern "RustQt" {
    // QObject 定义
    // 我们告诉 CXX-Qt 我们想要一个名为 MyObject 的 QObject 类
    // 基于 Rust 结构体 MyObjectRust。
    #[qobject]
    #[qml_element]
    #[qproperty(i32, number)]
    #[qproperty(QString, string)]
    #[namespace = "my_object"]
    type MyObject = super::MyObjectRust;
}

如果未在属性上指定其他属性,CXX-Qt 将自动生成 setter 和 getter,以及一个 “changed” 信号。属性的类型和名称必须与内部 Rust 结构体中的字段匹配。

#[derive(Default)]
pub struct MyObjectRust {
    number: i32,
    string: QString,
}

CXX-Qt 将生成以下函数:

C++Rust
settersetset_
getterget
changed signalChanged_changed

与任何信号一样,CXX-Qt 将在 Rust 端生成相应的连接函数:

  • connect: connect__changed

  • on: on__changed

其中 是属性的名称。

这些 setter 和 getter 确保每次编辑属性时都会发出 changed 信号。

自定义属性

如果自动生成的函数不适用于你的用例,你可以禁用 CXX-Qt 的自动生成并编写完全自定义的属性。例如,如果你的属性不对应于内部 Rust 结构体中的任何单个字段,则可能需要这样做。

你可以使用标志指定自定义 getter、setter 和通知信号,例如:#[qproperty(TYPE, NAME, READ = myGetter, WRITE = mySetter, NOTIFY = myOnChanged)]。

📝 注意:标志的键使用全大写字母,如 Qt 版本的 `qproperty`。

可以组合使用标志或完全省略某些标志,但如果指定了任何标志,则必须包含 READ 标志。

如果为标志指定了自定义函数,则必须在桥接中声明该函数,并且必须存在相应的实现。

某些标志可以在不指定函数的情况下传递(例如 READ 和 READ=…)。对于这些标志,如果未提供函数,CXX-Qt 将自动生成实现,如上一节所述。例如,#[qproperty(i32, num, READ)] 将自动生成一个名为 get_num 的 getter 函数(在 Rust 中)和 getNum(在 C++ 中)。因此,#[qproperty(i32, num)] 只是 #[qproperty(i32, num, READ, WRITE, NOTIFY)] 的简写。

此外,可以像其他项目上的属性一样使用 cxx_name 和 rust_name。例如,#[qproperty(i32, num, cxx_name = “numberProp”)]。

示例

  • #[qproperty(TYPE, NAME, READ)]:具有自动生成的 getter 的只读属性。

  • #[qproperty(TYPE, NAME, READ = myGetter, WRITE, NOTIFY)]:提供自定义 getter,但自动生成 setter 和 changed 信号。

  • #[qproperty(TYPE, NAME)]:是 #[qproperty(TYPE, NAME, READ, WRITE, NOTIFY)] 的简写。

  • #[qproperty(TYPE, NAME, WRITE)]:错误,因为需要 READ 标志。

可用标志

  • READ 或 READ = my_getter:指定属性应为可读的(如果传递了标志,则始终需要),带有可选的用户定义 getter。

  • WRITE 或 WRITE = my_setter:指定属性应为可写的,带有可选的用户定义 setter。

  • NOTIFY 或 NOTIFY = my_on_changed:指定属性应在更改时发出通知信号,带有可选的用户定义信号名称。

  • CONSTANT:指定属性应为常量(意味着 getter 对于该特定实例始终返回相同的值)。

CONSTANT 不可用于使用 WRITE 或 NOTIFY 的属性,并且不会编译。

  • REQUIRED:指定属性必须由类的用户设置,在 QML 中很有用,因为除非设置了属性,否则无法实例化类。

  • FINAL:指定属性不会被派生类覆盖。

  • RESET = my_reset:指定重置属性为默认值的函数,必须提供用户函数,否则不会编译。

  • cxx_name = “myCxxName”:指定在 C++ 端使用的替代名称,适用于属性名称以及自动生成的函数。

  • rust_name = “my_rust_name”:指定在 Rust 端使用的替代名称,适用于属性名称以及自动生成的函数。

方法

任何带有 self 参数的签名都被解释为 Rust 方法,并暴露给给定类型的 C++ 方法。类型必须是共享引用 self: &T 或固定可变引用 self: Pin<&mut T>,其中 T 是 QObject 类型。

unsafe extern "RustQt" {
    /// 仅 C++ 方法,返回红色值
    #[cxx_name = "redValue"]
    fn red_value(self: &RustInvokables) -> f32;
}

然后在桥接外部正常编写方法的实现。

impl qobject::RustInvokables {
    /// 仅 C++ 方法,返回红色值
    pub fn red_value(&self) -> f32 {
        self.red
    }
}
注意这里使用 `impl qobject::T` 而不是 `impl T`,其中 `qobject` 是桥接模块名称。

可调用方法

可以在签名上指定 #[qinvokable] 属性,以在 C++ 中将其暴露为 Q_INVOKABLE。

unsafe extern "RustQt" {
    /// 不可变的可调用方法,返回 QColor
    #[qinvokable]
    #[cxx_name = "loadColor"]
    fn load_color(self: &RustInvokables) -> Result<QColor>;

    /// 可变的可调用方法,存储颜色
    #[qinvokable]
    #[cxx_name = "storeColor"]
    fn store_color(self: Pin<&mut RustInvokables>, red: f32, green: f32, blue: f32);

    /// 可变的可调用方法,使用枚举存储颜色
    #[qinvokable]
    #[cxx_name = "storeColorWithEnum"]
    fn store_color_with_enum(self: Pin<&mut RustInvokables>, color: Color);

    /// 无可变参数的可调用方法,重置颜色
    #[qinvokable]
    fn reset(self: Pin<&mut RustInvokables>);
}

然后实现与非可调用方法没有区别。

impl qobject::RustInvokables {
    /// 不可变的可调用方法,返回 QColor
    pub fn load_color(&self) -> Result<QColor, i32> {
        Ok(self.as_qcolor())
    }

    /// 可变的可调用方法,存储颜色
    pub fn store_color(self: Pin<&mut Self>, red: f32, green: f32, blue: f32) {
        self.store_helper(red, green, blue);
    }

    /// QENUMS!
    pub fn store_color_with_enum(self: Pin<&mut Self>, color: qobject::Color) {
        use qobject::Color;
        let (r, g, b) = match color {
            Color::Red => (1.0, 0.0, 0.0),
            Color::Green => (0.0, 1.0, 0.0),
            Color::Blue => (0.0, 0.0, 1.0),
            _ => (0.0, 0.0, 0.0),
        };
        self.store_helper(r, g, b);
    }

    /// 无可变参数的可调用方法,重置颜色
    pub fn reset(self: Pin<&mut Self>) {
        self.store_helper(0.0, 0.4667, 0.7843);
    }
}

继承

可以通过 #[inherit] 属性访问基类上已存在的方法或信号。

有关文档,请参阅 继承页面。

说明符
生成的方法可以具有实现继承所需的 C++ 说明符。

C++ 关键字CXX-Qt 属性
override#[cxx_override]
virtual#[cxx_virtual]
final#[cxx_final]

这些说明符作为方法签名上的属性指定。

rust

unsafe extern “RustQt” {
#[qinvokable]
#[cxx_override]
fn data(self: &CustomBaseClass, index: &QModelIndex, role: i32) -> QVariant;
}

### 信号
qsignal 属性用于在 extern "RustQt" 块中为 QObject 定义信号。

```rust
unsafe extern "RustQt" {
    /// 当连接发生时发出的 Q_SIGNAL
    #[qsignal]
    fn connected(self: Pin<&mut RustSignals>, url: &QUrl);

    /// 当断开连接发生时发出的 Q_SIGNAL
    #[qsignal]
    fn disconnected(self: Pin<&mut RustSignals>);

    /// 当发生错误时发出的 Q_SIGNAL
    #[qsignal]
    fn error(self: Pin<&mut RustSignals>, message: QString);
}

对于 extern 块中的每个函数签名,CXX-Qt 将在相应的 QObject 上生成一个信号。如果函数有参数,它们将成为相应信号的参数。信号函数不需要手动实现。

如果信号在 QObject 的基类上定义,则可以使用 #[inherit],这将导致 CXX-Qt 访问基类中现有的 Q_SIGNAL。

完整示例可以在 qml 特性示例 中找到。

📝 注意:可以在信号上使用 `#[cxx_name="..."]` 和 `#[rust_name="..."]` 来声明 C++ 和 Rust 中的不同名称。
📝 注意:使用 `pub(self)` 作为信号的可见性允许声明私有信号。

连接到信号

对于每个信号,CXX-Qt 将生成两个方法来连接到它。

  1. on_<signal_name>

  2. connect_<signal_name>

on_<signal_name> 方法将处理函数作为参数,当信号发出时将调用该函数。该处理函数的第一个参数是发出信号的 QObject,其余参数是信号参数。

connect_<signal_name> 函数还接受 Qt 连接类型作为参数。

let connections = [
    qobject.as_mut().on_connected(|_, url| {
        println!("Connected: {}", url);
    }),
    qobject.as_mut().on_disconnected(|_| {
        println!("Disconnected");
    }),
    // 演示使用不同连接类型进行连接
    qobject.as_mut().connect_error(
        |_, message| {
            println!("Error: {}", message);
        },
        ConnectionType::QueuedConnection,
    ),
];
qobject.as_mut().rust_mut().connections = Some(connections);

每个连接返回一个 QMetaObjectConnectionGuard,它是 QMetaObject::Connection 的 RAII 包装器,并在守卫被丢弃时自动断开连接。这类似于 C++ 的 std::lock_guard、std::unique_ptr 或 Rust 的 Box。

示例:

// 通过将 connections 设置为 None,我们触发连接的丢弃
// 这将导致断开连接
qobject.as_mut().rust_mut().connections = None;

如果你不想存储 QMetaObjectConnectionGuard,可以调用 release,这将将其转换为内部的 QMetaObjectConnection,它是 QMetaObject::Connection 的直接包装器,不会在丢弃时断开连接。

📝 注意:`QMetaObjectConnection` 有一个 `disconnect` 方法,可以稍后手动调用。

发出信号

调用 extern “RustQt” 块中定义的函数签名以发出信号。

请注意,这些函数是在生成的 QObject qobject::T 上定义的,因此可以从任何可变的 #[qinvokable] 中调用。

该函数将立即发出信号。根据连接类型,连接的槽将立即调用或从事件循环中调用(参见不同的连接类型)。要将调用排队到 Qt 事件循环的下一个周期,可以使用 CxxQtThread。

信号继承

如果信号在 QObject 的基类上定义,则可以使用 #[inherit] 属性来指示 CXX-Qt 不需要在 C++ 中创建 Q_SIGNAL。

unsafe extern "RustQt" {
    /// 从 QAbstractListModel 基类继承 DataChanged 信号
    #[inherit]
    #[qsignal]
    #[cxx_name = "dataChanged"]
    fn data_changed(
        self: Pin<&mut CustomBaseClass>,
        top_left: &QModelIndex,
        bottom_right: &QModelIndex,
        roles: &QVector_i32,
    );
}

相关文章:

  • JAVA开发:实例成员与静态成员
  • MySQL 优化详解:从基础到高级全面指南
  • PSA方法计算器(PSA Method Calculator): 鼠标完美灵敏度测试网站
  • Spring Boot属性设置方法及优先级完整说明+表格对比
  • PyTorch图像预处理--Compose
  • 分别通过 JNI和纯java 实现对 Windows 注册表的增删改查操作的完整示例,包含详细注释和步骤说明
  • 【设计模式】工厂模式详解-----简单工厂模式、工厂方法模式、抽象工厂模式
  • 当一个按键“摆烂”时,需要更换整个键盘吗?
  • Selenium之简介
  • Json冲突崩溃问题
  • Logit-Laplace 分布:解决图像生成中像素值范围匹配问题的创新分布
  • cellnet框架概述
  • SQL 函数
  • stm32 f1 外接大功率负载方案 6DI/8DO/4AI/1AO
  • Node.js 模块系统
  • 【Linux】MAC帧
  • ClickHouse常见问题总结
  • DeepSeek概述
  • Oracle常用分析诊断工具(9)——ADDM
  • Linux——进程信号(1)(signal与sigaction)
  • 《探秘海昏侯国》数字沉浸特展亮相首届江西文化旅游产业博览交易会
  • 十二届上海市委第六轮巡视全面进驻,巡视组联系方式公布
  • 国务院食安办:加强五一假期食品生产、销售、餐饮服务环节监管
  • 国务院任免国家工作人员:颜清辉任人社部副部长
  • 揭秘神舟十九号返回舱“软着陆”关键:4台发动机10毫秒内同时点火
  • 摩根大通任命杜峯为亚太区副主席,加码中国市场业务布局