QML 类型编译器
Qt QML Compiler(qmltc )是 Qt 随附的一款工具,用于将 QML 类型转换为 C++ 类型,这些类型作为用户代码的一部分进行预编译。与基于 `QQmlComponent` 的对象创建相比,使用 qmltc 能够为编译器提供更多的优化机会,从而提升运行时性能。 qmltc 是 Qt Quick Compiler 工具链的一部分。
按设计,qmltc 会输出面向用户的代码。该代码应由 C++ 应用程序直接使用,否则您将无法获得任何收益。生成的代码本质上取代了QQmlComponent 及其 API,用于从 QML 文档创建对象。您可以在《在 QML 应用程序中使用 qmltc》和《生成的输出基础知识》中找到更多信息。
要启用 qmltc:
- 为您的应用程序创建一个合适的 QML 模块。
- 调用 qmltc,例如通过CMake API。
#include将生成的头文件包含到应用程序源代码中。- 实例化生成的类型的对象。
在此工作流中,qmltc 通常在构建过程中运行。因此,当 qmltc 拒绝某个 QML 文档时(无论是由于错误、警告,还是因为 qmltc 尚未支持的结构),构建过程都会失败。 这类似于在创建 QML 模块时启用自动生成代码检查目标,然后尝试“构建”它们以运行 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 驱动的构建流程,因为该流程以正确的 Qt Qml 模块及其基础设施为核心。
添加 qmltc 编译的简便方法是,在为应用程序创建 QML 模块时使用专用的CMake API。考虑一个简单的应用程序目录结构:
.
├── 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:
find_package(Qt6 COMPONENTS QmlPrivate QuickPrivate)
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) 也是如此。这也意味着您可以使用QQmlEngine methods 来影响QML的行为。 不过,这里有一些注意事项。与基于QQmlComponent 的对象创建不同,qmltc 本身在将代码编译为 C++ 时并不依赖QQmlEngine 。例如,QQmlEngine::addImportPath("/foo/bar/") —— 通常会生成一个需要扫描的额外导入路径 —— 会被 qmltc 的提前编译过程完全忽略。
注意:若需在 qmltc 编译中添加导入路径,请考虑改用CMake 命令的相关参数。
通常,您可以这样理解:QQmlEngine 涉及应用程序的运行过程,而qmltc则不然,因为它在您的应用程序甚至尚未编译之前就已开始运行。 由于 qmltc 不会尝试检查应用程序的 C++ 源代码,因此它无法了解您作为用户所进行的某些类型的 QML 操作。 与其使用 `QQmlEngine ` 及相关运行时例程向 QML 暴露类型、添加导入路径等,实际上,您需要创建行为规范的 QML 模块,并使用声明式的 QML 类型注册。
警告:尽管 qmltc 与QQmlEngine 紧密协作并生成 C++ 代码,但生成的类无法进一步向 QML 公开,也无法通过QQmlComponent 使用。
生成的输出基础知识
qmltc 旨在与现有的 QML 执行模型保持兼容。这意味着生成的代码大致等同于QQmlComponent 的内部设置逻辑,因此您应该能够像现在一样——通过直观检查相应的 QML 文档——来理解 QML 类型的行为、语义和 API。
不过,生成的代码仍然有些令人困惑,特别是考虑到您的应用程序应在 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 专用的元对象系统宏、用于hello 属性的Q_PROPERTY 装饰器、Q_INVOKABLE 的 C++ 打印函数,以及一个常规的 Qt 信号定义。同样地,qmltc 会将给定的 HelloWorld 类型转换为大致如下所示的代码:
class HelloWorld : public QObject
{
Q_OBJECT
QML_ELEMENT
Q_PROPERTY(QString hello READ hello WRITE setHello BINDABLE bindableHello)
public:
HelloWorld(QQmlEngine* engine, QObject* parent = nullptr, [[maybe_unused]] qxp::function_ref<void(PropertyInitializer&)> initializer = [](PropertyInitializer&){});
Q_SIGNALS:
void created();
public:
QString hello();
void setHello(const QString& hello_);
QBindable<QString> bindableHello();
Q_INVOKABLE void printHello(passByConstRefOrValue<QString> prefix, passByConstRefOrValue<QString> suffix);
// ...
};尽管生成的类型的具体细节可能有所不同,但其通用特征仍然存在。例如:
- 文档中的 QML 类型会根据编译器可见的信息转换为 C++ 类型。
- 属性会被转换为带有Q_PROPERTY 声明的C++属性。
- JavaScript 函数将转换为带有 `
Q_INVOKABLE` 的 C++ 函数。 - Qt 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 中实现应用逻辑,并使用向 QML 公开的手写 C++ 类型,仅将 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、生成的代码或其他相关部分中遇到错误。遇到此类情况,我们鼓励您提交错误报告。
© 2026 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.