QML类型编译器
QML类型编译器(qmltc
),是Qt自带的工具,可以将QML类型转换为C++类型,这些类型将以即时编译的形式作为用户代码的一部分。使用qmltc
可以因编译器有更多的优化机会而导致更好的运行时性能,相对于基于QQmlComponent的对象创建。qmltc是Qt Quick编译器工具链的一部分。
按照设计,qmltc
会输出面向用户的面板代码。该代码预期会直接由C++应用程序使用,否则您不会看到任何好处。这个生成的代码本质上替换了QQmlComponent及其API,用于从QML文档中创建对象。您可以在在QML应用程序中使用qmltc和生成输出基础下找到更多信息。
为了启用qmltc
在这个工作流程中,qmltc通常在构建过程中运行。因此,当qmltc拒绝一个QML文档(无论是因为错误或警告,还是因为qmltc尚不支持的结构),构建过程将失败。这类似于您在创建QML模块时启用自动生成linting目标并尝试“构建”以运行qmllint时接收qmllint错误。
警告:qmltc
目前正在技术预览阶段,可能无法编译任何任意的QML程序(有关更多详细信息,请参阅已知限制)。当qmltc
失败时,不会生成任何内容,因为您的应用程序无法合理地使用qmltc
的输出。如果您的程序包含错误(或无法解决的警告),则应修复它们以启用编译。一般规则是要遵循最佳实践并遵循qmllint的建议。
注意:qmltc
不保证生成的C++代码在过去的或未来的版本(即使是补丁版本)之间保持API、源代码或二进制兼容性。此外,使用Qt的QML模块编译的应用程序将需要链接到私有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编译的简单方法是在创建应用时作为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 ) # (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结合以在屏幕上绘制。给定一个myApp.qml
文件,应用代码(在两种情况下)通常如下所示
#include <QtQml/qqmlcomponent.h> QGuiApplication app(argc, argv); app.setApplicationDisplayName(QStringLiteral("This example is powered by QQmlComponent :(")); QQmlEngine e; 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; QQuickWindow window; QScopedPointer<QmltcExample::myApp> documentRoot(new QmltcExample::myApp(&e)); documentRoot->setParentItem(window.contentItem()); window.setHeight(documentRoot->height()); window.setWidth(documentRoot->width()); // ... window.show(); app.exec();
QML引擎
生成的代码使用QQmlEngine与QML文档的动态部分交互,主要是JavaScript代码。为了使它工作,无需特殊安排。任何传递给qmltc生成的类对象的QQmlEngine构造函数的QQmlEngine实例都应该像QQmlComponent(engine)
一样正常工作。这也意味着您可以使用影响QML行为的QQmlEngine方法。但是,也存在一些注意事项。与基于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 类型 behavior、语义和 API —— 通过查看相应的 QML 文档来可视化检查。
然而,生成的代码仍然有些令人困惑,特别是考虑到您的应用程序应该直接在 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); 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 定义的类型元素,但理解此类代码可能是一项挑战。
已知限制
尽管涵盖了许多常见的 QML 功能,但 qmltc 仍在开发初期,还有一些内容尚未得到支持。
由 QML 定义的类型(如 QtQuick.Controls
)组成的导入 QML 模块可能无法正确编译,即使这些由 qmltc 编译的 QML 定义的类型。目前,您可以可靠地使用 QtQml
和 QtQuick
模块以及任何只包含暴露给 QML 的 C++ 类的其他 QML 模块。
除了这些,还有一些更基本的特殊性需要考虑
- Qt 的 QML 模块通常依赖于 C++ 库来执行繁重的任务。通常情况下,这些库不提供公共 C++ API(因为它们的主要用途是通过 QML)。对于 qmltc 的用户来说,这意味着他们的应用程序需要链接到私有 Qt 库。
- 由于 qmltc 代码生成的特性,QML 插件在编译过程中无法使用。相反,使用插件的 QML 模块必须确保插件数据在编译时是可访问的。这样,这些 QML 模块将有可选的插件。在大多数情况下,编译时信息可以通过包含 C++ 声明的头文件和包含 C++ 定义的链接库来提供。负责用户代码的是(通常通过 CMake)包含头文件路径并链接到 QML 模块库。
注意:鉴于编译器的预览状态,您也可能会在 qmltc、生成的代码或其他相关部分中遇到错误。我们鼓励您在这种情况下提交错误报告。
© 2024 Qt 公司有限公司。此处包含的文档贡献属于各自所有者的版权。此处提供的文档根据由自由软件基金会发布的 GNU 自由文档许可证版本 1.3 的条款获得许可。Qt 以及相应的商标是芬兰的 Qt 公司及其全球附属公司的商标。所有其他商标均为其各自所有者的财产。