在本示例中,我们将演示如何使用CMake将CXX-Qt代码集成到C++应用程序中。Cargo将CXX-Qt代码构建为静态库,然后CMake将其链接到C++可执行文件中。
我们首先需要修改项目结构,以分离项目的不同部分。
tutorial
cpp
qml
rust
将Rust项目移动到rust文件夹中。将qml文件夹移回顶层。
C++可执行文件
为了启动我们的QML应用程序,我们需要一个包含普通main函数的小型main.cpp文件。将其放在cpp文件夹中,以明确区分C++和Rust代码:
#include <QtGui/QGuiApplication>
#include <QtQml/QQmlApplicationEngine>int main(int argc, char* argv[]){QGuiApplication app(argc, argv);QQmlApplicationEngine engine;const QUrl url(QStringLiteral("qrc:/qt/qml/com/kdab/cxx_qt/demo/qml/main.qml"));QObject::connect(&engine,&QQmlApplicationEngine::objectCreated,&app,[url](QObject* obj, const QUrl& objUrl) {if (!obj && url == objUrl)QCoreApplication::exit(-1);},Qt::QueuedConnection);engine.load(url);return app.exec();
}
你可以根据需要添加更多的C++代码。
在C++中使用Rust QObjects
对于我们在Rust中定义的每个#[cxx_qt::bridge],CXX-Qt都会生成一个对应的C++头文件。要包含任何生成的文件,请使用crate的名称作为包含目录。头文件的名称将是文件夹名称的组合,加上你的#[cxx_qt::bridge]的输入Rust文件名,后跟.cxxqt.h。因此,在我们的情况下:#include <qml_minimal/src/cxxqt_object.cxxqt.h>
📝 注意:任何相对于Cargo.toml文件的文件夹都会被考虑,因此src文件夹也会被包含。
包含生成的头文件后,我们可以像访问任何其他C++类一样访问MyObject C++类。继承它,将信号和槽连接到它,将其放入QVector中,随心所欲地使用它。这就是CXX-Qt的强大之处。
Cargo设置
在我们开始使用CMake构建Qt之前,我们首先需要让我们的Cargo构建准备好。如果你已经使用例如cargo new --lib qml_minimal或cargo init --lib [folder]命令生成了你的项目,你的Cargo.toml应该看起来像这样:
[package]
name = "qml_minimal"
version = "0.1.0"
edition = "2021"[dependencies]
我们需要做几件事:
- 指示Cargo创建一个静态库
- 添加cxx、cxx-qt以及cxx-qt-lib作为依赖项
- 添加cxx-qt-build作为构建依赖项
如果你已经按照Cargo设置进行了操作,大部分内容应该已经完成。确保将crate-type更改为"staticlib"!
最终,你的Cargo.toml应该看起来类似于这样。
[package]
name = "qml_minimal"
version = "0.1.0"
authors = ["Andrew Hayzen <andrew.hayzen@kdab.com>","Gerhard de Clercq <gerhard.declercq@kdab.com>","Leon Matthes <leon.matthes@kdab.com>"
]
edition = "2021"
license = "MIT OR Apache-2.0"# 这将指示Cargo创建一个静态库,CMake可以链接到它
[lib]
crate-type = ["staticlib"][dependencies]
cxx = "1.0.95"
cxx-qt = "0.7"
cxx-qt-lib = { version="0.7", features = ["qt_full"] }[build-dependencies]
# link_qt_object_files特性是静态链接Qt 6所必需的。
cxx-qt-build = { version = "0.7", features = [ "link_qt_object_files" ] }
我们还需要在Cargo.toml旁边添加一个名为build.rs的脚本:
如果你已经按照Cargo构建教程进行了操作,只需修改现有的build.rs文件。
use cxx_qt_build::{CxxQtBuilder, QmlModule};fn main() {CxxQtBuilder::new().qml_module(QmlModule {uri: "com.kdab.cxx_qt.demo",rust_files: &["src/cxxqt_object.rs"],qml_files: &["../qml/main.qml"],..Default::default()}).build();
}
这是在构建时生成和编译我们的MyObject类的C++代码的地方。
每个使用#[cxx_qt::bridge]宏的Rust源文件都需要包含在此脚本中。在我们的情况下,这仅是src/cxxqt_object.rs文件。
这也是定义QML模块的地方,带有QML URI和版本。然后,模块中的文件和资源以与qt_add_qml_module CMake函数相同的方式公开。
注意,为了使CXX-Qt工作,必须找到qmake可执行文件。这是因为CXX-Qt依赖于qmake来定位系统上必要的Qt库和头文件。
通常,CXX-Qt提供的用于导入crate的CMake代码应该已经处理了这一点。
要覆盖qmake的路径,你可以将QMAKE选项传递给cxx_qt_import_crate,以确保CMake和Cargo使用相同的Qt二进制文件。
我们还需要删除src/main.rs并将其替换为src/lib.rs文件。该文件只需要包含一行:
pub mod cxxqt_object;
这只是确保我们的Rust模块包含在我们的库中。
你也可以在库中添加其他Rust模块。
CMake设置
现在在你的项目文件夹的根目录中添加一个CMakeLists.txt文件。像任何其他使用Qt的C++项目一样启动CMakeLists.txt文件。对于此示例,我们支持使用CMake的Qt5和Qt6:
cmake_minimum_required(VERSION 3.24)project(example_qml_minimal)# Rust始终在*-msvc目标上链接到非调试Windows运行时
# 注意最好在命令行上设置此选项,以确保所有目标一致
# https://github.com/corrosion-rs/corrosion/blob/master/doc/src/common_issues.md#linking-debug-cc-libraries-into-rust-fails-on-windows-msvc-targets
# https://github.com/rust-lang/rust/issues/39016
if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreadedDLL")
endif()set(CMAKE_AUTOMOC ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)set(CXXQT_QTCOMPONENTS Core Gui Qml QuickControls2 QuickTest Test)set(CXXQT_QTCOMPONENTS ${CXXQT_QTCOMPONENTS} QmlImportScanner)
if(NOT USE_QT5)find_package(Qt6 COMPONENTS ${CXXQT_QTCOMPONENTS})
endif()
if(NOT Qt6_FOUND)find_package(Qt5 5.15 COMPONENTS ${CXXQT_QTCOMPONENTS} REQUIRED)
endif()
使用FetchContent下载CXX-Qt的CMake代码:
find_package(CxxQt QUIET)
if(NOT CxxQt_FOUND)include(FetchContent)FetchContent_Declare(CxxQtGIT_REPOSITORY https://github.com/kdab/cxx-qt-cmake.gitGIT_TAG v0.7.0)FetchContent_MakeAvailable(CxxQt)
endif()
这为你提供了几个围绕Corrosion的包装器,Corrosion是一个用于将Rust库集成到CMake中的工具:
cxx_qt_import_crate - corrosion_import_crate的包装器。它支持与corrosion_import_crate相同的参数,并带有三个新参数:
- QT_MODULES(必需) - 要链接到的Qt模块。在此处指定相应的CMake目标。
- CXX_QT_EXPORT_DIR(可选) - 手动指定CXX-Qt工件将导出到的路径。
- 这通常不是必需的。但是,如果你在同一CMake构建配置中使用不同的特性集导入相同的crate,则需要指定单独的CXX_QT_EXPORT_DIR,以避免多个版本的crate导出到同一目录。
QMAKE(可选) - 覆盖QMAKE可执行文件的路径
cxx_qt_import_qml_module - 此函数导入QML模块作为新目标。它需要以下参数:
TARGET_NAME - 指定此函数将创建的CMake目标的名称
URI - 要导入的qml模块的URI - 这需要与CxxQtBuilder::qml_module调用中的URI完全匹配。
SOURCE_CRATE 导出QML模块的crate(此crate必须已使用cxx_qt_import_crate导入)。
- 这通常不是必需的。但是,如果你在同一CMake构建配置中使用不同的特性集导入相同的crate,则需要指定单独的CXX_QT_EXPORT_DIR,以避免多个版本的crate导出到同一目录。
# CXX-Qt(使用Corrosion)创建一个与crate同名的CMake目标。
cxx_qt_import_crate(MANIFEST_PATH rust/Cargo.toml CRATES qml_minimal QT_MODULES Qt::Core Qt::Gui Qt::Qml Qt::QuickControls2)cxx_qt_import_qml_module(qml_minimal_qml_moduleURI "com.kdab.cxx_qt.demo"SOURCE_CRATE qml_minimal)```
这将创建两个新的CMake目标:qml_minimal - 由我们的crate导出的静态库
qml_minimal_qml_module - 由我们的crate导出的QML模块_qml_module目标将自动链接到qml_minimal目标,因此链接到_qml_module对于我们的可执行目标来说已经足够
最后,我们可以创建CMake可执行目标并将其链接到我们的crate:```cmake
add_executable(example_qml_minimal cpp/main.cpp)# 链接到qml模块,该模块又链接到Rust qml_minimal库
target_link_libraries(example_qml_minimal PRIVATE qml_minimal_qml_module)# 如果我们使用静态链接的Qt,则需要导入任何qml插件
qt_import_qml_plugins(example_qml_minimal)
你的项目现在应该具有类似于以下结构:
$ tree -I target/ -I tests
.
├── CMakeLists.txt
├── cpp
│ └── main.cpp
├── qml
│ └── main.qml
└── rust├── build.rs├── Cargo.toml└── src├── cxxqt_object.rs└── lib.rs```
像构建任何其他CMake项目一样构建项目:```bash
$ cmake -S . -B build
$ cmake --build build
如果由于任何原因失败,请查看examples/qml_minimal文件夹,其中包含完整的示例代码。
现在应该配置并编译我们的项目。如果成功,你现在可以运行我们的小项目。
$ ./build/examples/qml_minimal/example_qml_minimal
你现在应该看到显示我们的MyObject状态的两个标签,以及调用我们的两个Rust函数的两个按钮。
Windows上的MSVC
如果你在Windows上使用MSVC生成器构建CXX-Qt,你需要确保在CMake中设置了set(CMAKE_MSVC_RUNTIME_LIBRARY “MultiThreadedDLL”)(或使用-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreadedDLL标志)在构建Debug配置时。此标志是确保使用正确的C运行时库所必需的。然后你可以使用cmake --build build --config Debug进行构建。
此问题是由cc crate中的一个错误引起的(如在此拉取请求中所述),该错误尚未合并。具体来说,问题是cc生成的代码始终链接到MultiThreaded运行时,即使在Debug模式下构建时也是如此。我们希望一旦cc crate修复合并并发布,此步骤将不再必要。