QML 类型编译器
QtML 类型编译器qmltc
是 Qt XML 随附的一个工具,用于将 QML 类型转换为 C++ 类型,并作为用户代码的一部分进行超前编译。与基于QQmlComponent 的对象创建相比,使用 qmltc 可为编译器提供更多优化机会,从而提高运行时性能。qmltc 是 Qt Quick Compiler工具链的一部分。
根据设计,qmltc 输出面向用户的代码。这些代码应该由 C++ 应用程序直接使用,否则你将看不到任何好处。这些生成的代码实质上取代了QQmlComponent 及其从 QML 文档创建对象的 API。你可以在QML 应用程序中使用 qmltc和Generated Output Basics 下找到更多信息。
要启用 qmltc,您需要
- 为你的应用程序创建一个合适的 QML 模块。
- 例如,通过CMake API 调用 qmltc。
#include
在应用程序源代码中加入生成的头文件。- 实例化生成类型的对象。
在此工作流程中,qmltc 通常在构建过程中运行。因此,当 qmltc 拒绝 QML 文档时(无论是由于错误或警告,还是由于 qmltc 尚不支持的构造),构建过程就会失败。这类似于当你在创建 QML 模块时启用自动生成 linting 目标,然后尝试 "构建 "它们以运行 qmllint 时,你会收到 qmllint 错误。
警告: qmltc 目前处于技术预览阶段,可能无法编译任意 QML 程序(更多详情,请参阅已知限制)。当 qmltc 失败时,不会生成任何内容,因为您的应用程序无法合理地使用 qmltc 输出。如果你的程序包含错误(或无法解决的警告),应将它们修正,以便能进行编译。一般规则是遵守最佳实践并遵循qmllint建议。
注意: qmltc
并不保证生成的 C++ 在过去或未来版本(甚至是补丁版本)之间保持 API、源代码或二进制兼容。此外,使用 Qt 的 QML 模块编译的 Qmltc 应用程序需要与私有 Qt API 进行链接。这是因为 Qt 的 QML 模块通常不提供公共 C++ API,因为它们的主要用途是通过 QML 实现的。
在 QML 应用程序中使用 qmltc
从构建系统的角度看,添加 qmltc 编译与添加 qml 缓存生成并无太大区别。简单地说,编译过程可以描述为
虽然真正的编译过程要复杂得多,但这张图捕捉到了 qmltc 使用的核心组件:QML 文件本身和包含 qmltypes 信息的 qmldir。比较简单的应用程序通常使用比较原始的 qmldir,但一般来说,qmldir 可能比较复杂,它提供了重要的、包装精美的类型信息,qmltc 依靠这些信息来执行 QML 到 C++ 的正确翻译。
不过,在 qmltc 的情况下,仅仅增加一个额外的构建步骤是不够的。还必须修改应用代码,以使用 qmltc 生成的类,而不是QQmlComponent 或其更高级的替代品。
用 qmltc 编译 QML 代码
从 Qt 6 开始,Qt 使用 CMake 来构建它的各种组件。我们鼓励用户项目也使用 CMake 来构建他们的 Qt 组件。要为您的项目添加开箱即用的 qmltc 编译支持,也需要 CMake 驱动的构建流程,因为该流程是以适当的 QML 模块及其基础结构为中心的。
添加 qmltc 编译的简单方法是使用专用的CMake API作为应用程序 QML 模块创建的一部分。考虑一个简单的应用程序目录结构:
. ├── CMakeLists.txt ├── myspecialtype.h // C++ type exposed to QML ├── myspecialtype.cpp ├── myApp.qml // main QML page ├── MyButton.qml // custom UI button ├── MySlider.qml // custom UI slider └── main.cpp // main C++ application file
那么 CMake 代码通常类似于下面这样:
# Use "my_qmltc_example" as an application name: set(application_name my_qmltc_example) # Create a CMake target, add C++ source files, link libraries, etc... # Specify a list of QML files to be compiled: set(application_qml_files myApp.qml MyButton.qml MySlider.qml ) # Make the application into a proper QML module: qt6_add_qml_module(${application_name} URI QmltcExample QML_FILES ${application_qml_files} # Compile qml files (listed in QML_FILES) to C++ using qmltc and add these # files to the application binary: ENABLE_TYPE_COMPILER NO_GENERATE_EXTRA_QMLDIRS ) # (qmltc-specific) Link *private* libraries that correspond to QML modules: target_link_libraries(${application_name} PRIVATE Qt::QmlPrivate Qt::QuickPrivate)
使用生成的 C++
与QQmlComponent 实例化的情况不同,qmltc 的输出是 C++ 代码,可被应用程序直接使用。一般来说,在 C++ 中构造一个新对象等同于通过QQmlComponent::create() 创建一个新对象。创建对象后,可以通过 C++ 对其进行操作,或将其与QQuickWindow 结合起来在屏幕上绘制。
如果编译后的类型暴露了一些必需的属性,`qmltc` 将要求在生成对象的构造函数中为这些属性设置一个初始值。
此外,qmltc 对象的构造函数可以提供一个回调函数来设置组件属性的初始值。
在myApp.qml
文件中,应用程序代码(两种情况都有)通常如下所示:
#include <QtQml/qqmlcomponent.h> QGuiApplication app(argc, argv); app.setApplicationDisplayName(QStringLiteral("This example is powered by QQmlComponent :(")); QQmlEngine e; // If the root element is Window, you don't need to create a Window. // The snippet is for the cases where the root element is not a Window. QQuickWindow window; QQmlComponent component(&e); component.loadUrl( QUrl(QStringLiteral("qrc:/qt/qml/QmltcExample/myApp.qml"))); QScopedPointer<QObject> documentRoot(component.create()); QQuickItem *documentRootItem = qobject_cast<QQuickItem *>(documentRoot.get()); documentRootItem->setParentItem(window.contentItem()); window.setHeight(documentRootItem->height()); window.setWidth(documentRootItem->width()); // ... window.show(); app.exec();
#include "myapp.h" // include generated C++ header QGuiApplication app(argc, argv); app.setApplicationDisplayName(QStringLiteral("This example is powered by qmltc!")); QQmlEngine e; // If the root element is Window, you don't need to create a Window. // The snippet is for the cases where the root element is not a Window. QQuickWindow window; QScopedPointer<QmltcExample::myApp> documentRoot( new QmltcExample::myApp(&e, nullptr, [](auto& component){ component.setWidth(800); })); documentRoot->setParentItem(window.contentItem()); window.setHeight(documentRoot->height()); window.setWidth(documentRoot->width()); // ... window.show(); app.exec();
QML 引擎
生成的代码使用QQmlEngine 与 QML 文档的动态部分(主要是 JavaScript 代码)交互。要做到这一点,不需要任何特殊安排。传给 qmltc 生成的类对象的构造函数的任何QQmlEngine 实例都应与QQmlComponent(engine)
一样正常工作。这也意味着您可以使用影响 QML 行为的QQmlEngine methods 。不过,也有一些注意事项。与基于QQmlComponent 的对象创建不同,在将代码编译成 C++ 时,qmltc 本身并不依赖于QQmlEngine 。例如,QQmlEngine::addImportPath("/foo/bar/")
- 通常会导致需要扫描的额外导入路径 - 会被超前的 qmltc 程序完全忽略。
注意: 要在 qmltc 编译中添加导入路径,可考虑使用CMake 命令的相关参数。
一般来说,你可以这样理解:QQmlEngine 涉及应用程序进程的运行,而 qmltc 不涉及,因为它在应用程序编译之前就已运行。由于 qmltc 不会反省应用程序的 C++ 源代码,因此它无法了解用户对 QML 的某些操作。实际上,你不需要使用QQmlEngine 和相关的运行时例程来向 QML 公开类型、添加导入路径等,而是需要创建良好的 QML 模块,并使用声明式 QML 类型注册。
警告 尽管 qmltc 与QQmlEngine 紧密配合并创建 C++ 代码,但生成的类不能进一步暴露给 QML,也不能通过QQmlComponent 使用。
生成输出基础
qmltc
qmltc 的目标是与现有的 QML 执行模型兼容。这意味着生成的代码大致等同于 的内部设置逻辑,因此您应该能像现在一样--通过直观检查相应的 QML 文档--理解 QML 类型的行为、语义和 API。QQmlComponent
不过,生成的代码仍然有些令人困惑,特别是考虑到您的应用程序应直接使用 C++ 端的 qmltc 输出。生成的代码分为两部分:CMake 生成文件结构和生成的 C++ 格式。前者将在qmltc 的 CMake API中介绍,后者将在此处介绍。
考虑一个简单的 HelloWorld 类型,它有一个hello
属性、一个打印该属性的函数,以及一个在创建该类型对象时发出的信号:
// HelloWorld.qml import QtQml QtObject { id: me property string hello: "Hello, qmltc!" function printHello(prefix: string, suffix: string) { console.log(prefix + me.hello + suffix); } signal created() Component.onCompleted: me.created(); }
当提供这个 QML 类型的 C++ 替代品时,C++ 类需要一个QML 特定的元对象系统宏、Q_PROPERTY hello
属性的装饰、Q_INVOKABLE
C++ 打印函数和一个常规的 Qt 信号定义。同样,qmltc 将把给定的 HelloWorld 类型翻译成大致如下的内容:
class HelloWorld : public QObject { Q_OBJECT QML_ELEMENT Q_PROPERTY(QString hello WRITE setHello READ hello BINDABLE bindableHello) public: HelloWorld(QQmlEngine* engine, QObject* parent = nullptr, [[maybe_unused]] qxp::function_ref<void(PropertyInitializer&)> initializer = [](PropertyInitializer&){}); Q_SIGNALS: void created(); public: void setHello(const QString& hello_); QString hello(); QBindable<QString> bindableHello(); Q_INVOKABLE void printHello(passByConstRefOrValue<QString> prefix, passByConstRefOrValue<QString> suffix); // ... };
尽管生成类型的具体细节可能有所不同,但通用的方面仍然存在。例如
- 文档中的 QML 类型会根据编译器可见信息翻译成 C++ 类型。
- 属性通过Q_PROPERTY 声明翻译成 C++ 属性。
- JavaScript 函数变成
Q_INVOKABLE
C++ 函数。 - QML 信号被转换成 C++ Qt 信号。
- QML 枚举通过
Q_ENUM
声明转换为 C++ 枚举。
另外一个细节是qmltc
生成类名的方式。给定 QML 类型的类名是从定义该类型的 QML 文档中自动推导出来的:不含扩展名的 QML 文件名(直到并不包括第一个.
,也称为基名)成为类名。文件名的大小写保留不变。因此,HelloWorld.qml
的结果是class HelloWorld
,helloWoRlD.qml
的结果是class helloWoRlD
。按照 QML 惯例,如果 QML 文档的文件名以小写字母开头,生成的 C++ 类将被假定为匿名类,并标记为QML_ANONYMOUS 。
目前,虽然生成的代码已可在 C++ 应用程序中使用,但一般应限制对生成的 API 的调用。相反,您最好用 QML/JavaScript 和手写的 C++ 类型来实现应用逻辑,并暴露给 QML,使用 qmltc 创建的类来实现简单的对象实例化。虽然生成的 C++ 能让你直接(通常也更快)访问 QML 定义的元素类型,但理解这些代码可能是个挑战。
已知限制
尽管 qmltc 涵盖了许多常见的 QML 功能,但它仍处于开发的早期阶段,有些功能尚未得到支持。
由 QML 定义类型(如QtQuick.Controls
)组成的导入 QML 模块可能无法正确编译,即使这些 QML 定义类型是由 qmltc 编译的。目前,您可以可靠地使用QtQml
和QtQuick
模块,以及任何其他只包含暴露于 QML 的 C++ 类的 QML 模块。
除此之外,还有一些更基本的特殊性需要考虑:
- Qt 的 QML 模块通常依赖 C++ 库来完成繁重的工作。这些库通常不提供公共 C++ API(因为它们的主要用途是通过 QML)。对于 qmltc 的用户来说,这意味着他们的应用程序需要链接到私有的 Qt 库。
- 由于 qmltc 代码生成的性质,QML 插件无法用于编译目的。相反,使用插件的 QML 模块必须确保在编译时能访问插件数据。这样,QML 模块就有了可选插件。在大多数情况下,编译时信息可通过头文件(含 C++ 声明)和可链接库(含 C++ 定义)提供。用户代码负责(通常通过 CMake)包含头文件的路径,并与 QML 模块库链接。
注意: 鉴于编译器的技术预览状态,您也可能在 qmltc、生成的代码或其他相关部分遇到错误。我们鼓励您在这种情况下提交错误报告。
© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.