QML类型编译:你没听说过多少的新Qt Quick编译器部分

QML Type Compilation: that new Qt Quick Compiler part you have not heard much about

QML类型编译:你没听说过多少的新Qt Quick编译器部分

Thursday March 03, 2022 by Andrei Golubev | Comments

​2022年3月3日星期四安 德烈·戈卢贝夫 | 评论

We have been recently talking about the QML to C++ compilation, but this was mostly describing the process of compiling your JavaScript code. Along with it, however, there is another compiler coming in Qt 6.3 - the QML type compiler (or qmltc for short), available as a tool in the qtdeclarative repository. This compiler is part of the Qt Quick Compiler technology and, complementing QML script compiler (qmlsc), it aims to look at the QML language from a different angle. In this blog post you should learn about this part of QML and the process of compiling your QML types to C++, no components left aside.

​我们最近一直在谈论QML到C++编译,但这主要是描述编译JavaScript代码的过程。然而,Qt6.3中还有另一个编译器——QML类型的编译器(简称qmltc),可以作为Qt声明性存储库中的工具使用。该编译器是Qt Quick编译器技术的一部分,是对QML脚本编译器(qmlsc)的补充,旨在从不同的角度看待QML语言。在这篇博客文章中,你应该了解QML的这一部分,以及编译你的QML类型到C++的过程,没有剩下的组件。

qmltc: what it is and what it is not

qmltc:是什么,不是什么

In a nutshell, qmltc takes your QML document and creates an analogous C++ source code for it. If there are no issues, you get a C++ class that you can interact with from your application's source code. If something is wrong, qmltc loudly fails and breaks your build. This is because the qmltc produces C++ code that is considered essential for your application, similarly to how MOC generates C++ meta-object system code that is essential for the types marked with Q_OBJECT or Q_GADGET. For this reason, qmltc is an optional, opt-in feature in Qt. Additionally, mind that the type compiler is in the Technology Preview stage, so not everything is guaranteed to work. Nonetheless, we highly encourage you to try qmltc out!

​简单地说,qmltc占用了你的QML文档,并为它创建了类似的C++源代码。如果没有问题,你就可以从你的应用程序的源代码中得到一个C++类。如果出现问题,qmltc会失败并停止您的构建。这是因为qmltc产生C++代码,这对于应用程序来说是必不可少的,类似于MOC如何生成必要的带Q_OBJECTQ_GADGET标记C++元对象系统代码。出于这个原因,qmltc是Qt中的可选、选择性加入功能。此外,请注意,类型编译器处于技术预览阶段,因此并非所有内容都能保证正常工作。尽管如此,我们强烈鼓励您尝试qmltc!

Since qmltc will not help you automagically, you have to modify your current application C++ code to get any benefit from qmltc compilation. Unlike a silently working QML cache generation, qmltc provides, in some sense, user-visible output. This output - per QML file - is a class that represents the C++ form of the QML document. The purpose of qmltc (and thus the generated code) is to replace the QQmlComponent-based runtime loading and creation of QML objects with an ahead-of-time procedure. In principle, using qmltc should deprecate most of the existing object creation logic and allow the transition to the use of the generated code directly. Consequently, this would give additional optimization opportunities centered around the application startup. There are surely caveats along the way but the general idea is viable.

由于qmltc不会自动帮助您,所以必须修改当前应用程序C++代码,以从qmltc编译中获益。与静默工作的QML缓存生成不同,qmltc在某种意义上提供了用户可见的输出。每个QML文件的输出是一个类,表示QML文档的C++形式。qmltc(以及由此生成的代码)的目的是用提前过程替换基于QQmlComponent的运行时加载和创建QML对象。原则上,使用qmltc应该放弃大多数现有的对象创建逻辑,并允许直接转换到使用生成的代码。因此,这将提供围绕应用程序启动的额外优化机会。当然,在这个过程中会有一些警告,但总体思路是可行的。

Having this picture in mind, the user application would have to replace the object creation code, porting away from QQmlComponent::create() to direct C++ object creation via C++ constructors. In this regard, there is a marginal difference between the two approaches from the application perspective with resulting objects looking more or less the same at the API level. At present, however, qmltc's output is not integrated well enough with higher-level abstractions such as QQmlApplicationEngine or QQuickView.

​考虑到这幅图片,用户应用程序将不得不替换对象创建代码,从QQmlComponent::create()中移植,以通过C++构造函数来指导C++对象创建。在这方面,从应用程序的角度来看,这两种方法之间存在细微的差异,结果对象在API级别上看起来或多或少是相同的。然而,目前,qmltc的输出没有与更高级别的抽象(如QQmlApplicationEngine或QQuickView)充分集成。

In pseudo-code, you can use both approaches side by side in the following way:

在伪代码中,可以通过以下方式同时使用这两种方法:


QScopedPointer createWithQQmlComponent(QQmlEngine *e)
{
    // Use the QML document available in QRC
    QQmlComponent component(e, QUrl(QStringLiteral("qrc:/ModuleUri/HelloWorld.qml")));
    return QScopedPointer(component.create());
}

#include "helloworld.h" // compiled HelloWorld.qml header
QScopedPointer createWithQmltc(QQmlEngine *e)
{
    // Note that we can also return QScopedPointer
    return QScopedPointer(new HelloWorld(e));
}

int main()
{
    // ...
    QQmlEngine e;
    auto oldStyleObject = createWithQQmlComponent(&e);
    auto newStyleObject = createWithQmltc(&e);
}

You can read more about qmltc and related matters in the Qt documentation snapshots.

​您可以在Qt文档快照中阅读有关qmltc和相关事项的更多信息。

Solving the build system problem

解决构建系统问题

As already mentioned, you have to explicitly opt into building your application's QML files with qmltc. To achieve this, one can employ the CMake API that adds the qmltc compilation step in Qt 6.3. Note that this API assumes that you have a proper QML module and thus use our latest-and-greatest CMake command to create such a module. This is intentional: in most cases, if there is no proper QML module, the type analysis will fail and so qmltc will fail as well (not only that but qmlsc, compiling the JavaScript, would silently fall back to the old cache generation model).

​如前所述,您必须明确选择使用qmltc构建应用程序的QML文件。为了实现这一点,可以使用CMake API,在Qt6.3中添加qmltc编译步骤。请注意,此API假定您有一个适当的QML模块,因此使用我们最新和最好的CMake命令来创建这样一个模块。这是有意为之的:在大多数情况下,如果没有合适的QML模块,类型分析将失败,因此qmltc也将失败(不仅如此,编译JavaScript的qmlsc也会自动退回到旧的缓存生成模型)。

Generally, writing a proper QML module and then using the qmltc compilation command should be sufficient. In certain cases, you might need to add extra import paths (e.g. when you have your own QML modules that reside outside of the default import location). Other than that, you are all set for success from the build system point of view. The other aspect is to actually use the generated output. It seems to be best described through an example.

通常,编写适当的QML模块,然后使用qmltc编译命令就足够了。在某些情况下,您可能需要添加额外的导入路径(例如,当您自己的QML模块位于默认导入位置之外时)。除此之外,从构建系统的角度来看,您都已经做好了成功的准备。另一个方面是实际使用生成的输出。通过一个例子来描述它似乎是最好的。

Using qmltc

使用qmltc

To better highlight the way qmltc works, we can create a rather trivial application. Since this is a simple showcase of the capabilities, there is no need for anything fancy. We propose the following two-component (roughly speaking) design: a) a text field, e.g. a "Hello, World" of sorts; b) a button with an onClick handler that picks a random number and shows that number in a text field. To make b) a bit more visual, we can also transform the random value into an RGB color triplet and show the resulting color on screen along with the textual representation of the original value. While not exceptionally complex, this is interesting enough.

为了更好地强调qmltc的工作方式,我们可以创建一个相当简单的应用程序。因为这是一个简单的功能展示,所以不需要任何花哨的东西。我们提出了以下两部分(粗略地说)设计:a)文本字段,例如“Hello,World”之类的;b) 带有onClick处理器的按钮,该处理程序选择一个随机数并在文本字段中显示该数。为了使b)更直观,我们还可以将随机值转换为RGB颜色三元组,并在屏幕上显示结果颜色以及原始值的文本表示。虽然并不特别复杂,但这已经足够有趣了。

We can lay out our items vertically one after another. Having the elements in mind, the user interface schematically could be:

我们可以一个接一个地垂直布置我们的物品。考虑到这些元素,用户界面可以是:

QML类型编译:你没听说过多少的新Qt Quick编译器部分_第1张图片

To create such a UI, we would need multiple building blocks in Qt. Most notably, Qt Quick QML module for several basic UI classes. Unfortunately, qmltc cannot compile Qt Quick Controls 2 at the moment, so button has to be custom. Yet, the button is rather easy: it is basically a rectangle with a clickable mouse area inside (all can be taken directly from the Qt Quick), add a text field to it, other bells and whistles and here you go. We leave it as an exercise to the reader here.

​要创建这样一个UI,我们需要Qt中的多个构建块。最值得注意的是,Qt Quick QML模块用于几个基本的UI类。不幸的是,qmltc目前无法编译Qt Quick Controls 2,所以按钮必须是定制的。然而,这个按钮相当简单:它基本上是一个矩形,里面有一个可点击的鼠标区域(所有内容都可以直接从Qt Quick中获取),添加一个文本字段,其他铃声和口哨,然后开始。我们把它作为练习留给读者。

To convert a random number to a color, we can create a small C++ class which would return us a QColor from the given double value. Exposing the class to QML and using the returned QColor as a value for the color property of our rectangle would make everything work. Although a bit over-complicated, adding C++ into the application would make things even more fun so it is worth it, well, at least from the perspective of this blog post!

​为了把一个随机数转换成一个颜色,我们可以创建一个小的C++类,它可以从给定的double值中返回一个QColor。将该类公开给QML,并使用返回的QColor作为矩形的color属性的值,这将使一切工作正常。虽然有点过于复杂,但是在应用程序中添加C++会使事情变得更有趣,所以它是值得的,至少从这个博客帖子的角度来看!

Note that the snippets used here represent a simplified (and slightly re-structured) version of the example from the qmltc's documentation. The source code of that example is available in the qtdeclarative repository.

​请注意,这里使用的代码片段代表了qmltc文档中示例的简化(稍微重新构造)版本。该示例的源代码可在QT声明性存储库中获得。

Project structure and CMake

项目结构和CMake

For the sake of simplicity, we can put all our files into the same directory. This flat structure also guarantees correct resulting QML module structure.

为了简单起见,我们可以将所有文件放在同一个目录中。这种平面结构还保证了正确的QML模块结构。


.
├── CMakeLists.txt
├── colorpicker.cpp         # random-value-to-color converter (.cpp file)
├── colorpicker.h           # random-value-to-color converter (.h file)
├── myApp.qml               # application's main QML document
├── MyButton.qml            # custom button
└── main.cpp                # application's main C++ file

Now, the assumption is that you are acquainted with CMake, so we do not cover how to create an executable, link libraries to it, etc. The interesting bits are creating the QML module and enabling qmltc:

现在,假设您熟悉CMake,因此我们不介绍如何创建可执行文件、将库链接到它等。有趣的是创建QML模块并启用qmltc:


# CMakeLists.txt
set(application_qml_files # QML files in the application
    myApp.qml
    MyButton.qml
)

# Create a QML module:
qt6_add_qml_module(my_qmltc_example # my_qmltc_example is the application name
    VERSION 1.0
    URI QmltcExample
    QML_FILES ${application_qml_files}
)

# Enable qmltc (which would automatically add generated C++ to our application):
qt6_target_compile_qml_to_cpp(my_qmltc_example
    QML_FILES ${application_qml_files}
)

As part of working with qmltc, the application should also link against the private Qt libraries. This is due to most of the C++ code for the QML types being private (and thus inaccessible through Qt::Qml and Qt::Quick targets in CMake). While we strongly discourage the use of private API, this is unfortunately necessary at present when working with qmltc. There is an implicit guarantee that qmltc would ensure that the generated code works correctly with the private API. Note that this is only true (and makes sense) as long as qmltc compilation is re-done when you downgrade or upgrade the Qt libraries. In this case of working with QtQml and QtQuick, we need to link against private counterparts of Qt::Qml and Qt::Quick:

作为使用qmltc的一部分,应用程序还应该链接到专用Qt库。这是因为QML类型中的大多数C++代码是私有的(因此不能在CMake中,通过Qt::QML和Qt::Quick访问)访问。虽然我们强烈反对使用私有API,但不幸的是,目前在使用qmltc时,这是必要的。qmltc将确保生成的代码与专用API正确工作,这是一个隐含的保证。请注意,只有在降级或升级Qt库时重新完成qmltc编译,这才是正确的(并且有意义)。在使用QtQml和QtQuick的情况下,我们需要链接Qt::QmlQt::Quick的私有对应项:


target_link_libraries(my_qmltc_example PRIVATE Qt::QmlPrivate Qt::QuickPrivate)

Application code

应用程式码

The most interesting part of our small application is the source code, QML and C++. Considering MyButton type as less important, let's focus on three things:

我们的小应用程序最有趣的部分是源代码、QML和C++。考虑到MyButton类型不那么重要,让我们关注三件事:

  • (since it is a C++ type) a class that creates a color from the random value
  • (因为它是C++类型)从随机值创建颜色的类
  • (since it defines the full UI structure) main QML document
  • (因为它定义了完整的UI结构)主QML文档
  • (since it contains an entry point of our program) main.cpp
  • (因为它包含我们程序的入口点)main.cpp

The logic behind the random-value-to-color converter (we call it a "color picker" below) is straightforward: given a double value in the [0.0; 1.0) range, scale it up to the RGB integer value range [0; 256 * 256 * 256) and then interpret the result as a QRgb value (passing it to the constructor of QColor). We can hide this logic behind a dedicated function. Thus, at the API-level our color picker might look similar to this (note that many details are omitted):

随机值到颜色转换器(下面我们称之为“color picker”)背后的逻辑很简单:给定[0.0;1.0)范围内的双精度值,将其放大到RGB整数值范围[0;256*256*256),然后将结果解释为QRgb值(将其传递给QColor的构造函数).我们可以将这种逻辑隐藏在专用函数后面。因此,在API级别,我们的颜色选择器可能与此类似(请注意,许多细节被省略):


// colorpicker.h
class MyColorPicker : public QObject
{
    Q_OBJECT
    QML_ELEMENT

    // stores a value in the range [0, 1); myApp.qml type sets this with Math.random()
    // 存储[0,1)范围内的值;myApp.qml type使用Math.random()设置该值
    Q_PROPERTY(double encodedColor READ encodedColor WRITE setEncodedColor /* ... */)

public:
    Q_INVOKABLE QColor decodeColor(); // returns a QColor for the internally stored encodedColor
                                      //返回内部存储的encodedColor的QColor
};

Note the QML_ELEMENT macro there that would automatically register the C++ type in QML (in practice it is a bit more complicated but this is the foundational part), making it available once import QmltcExample 1.0 is done. In reality, this way you also make the type tooling-friendly for qmllintqmlsc, and surely qmltc.

注意,QML_ELEMENT的宏在那里会自动登记QML类型中的C++(实际上它有点复杂,但这是基础部分),一旦增加import QmltcExample 1.0,就可以使用它。实际上,通过这种方式,您还可以使类型工具对qmlint、qmlsc,当然还有qmltc友好。

The main QML document would define the UI structure of our application, as well as implement the interactions between separate elements. To arrange the items within the window, anchors property, available in any Item-based QML type, can be used. We do omit the boilerplate related to this property here for brevity.

​主QML文档将定义应用程序的UI结构,并实现不同元素之间的交互。要在窗口中排列项目,可以使用anchors属性,该属性在任何基于项目的QML类型中都可用。为了简洁起见,我们省略了与这个属性相关的样板。


// myApp.qml
import QtQuick
import QmltcExample 1.0 // application's own QML module

Rectangle {
    id: window

    Text {
        font.pixelSize: 20
        text: "Hello, QML World!"
    }

    Text {
        id: rndText
        font.pixelSize: 25
        text: "0.00"
    }

    Rectangle {
        id: rndColorRect
        color: "black"

        MyColorPicker { // comes from C++
            id: colorPicker
            onEncodedColorChanged: rndColorRect.color = colorPicker.decodeColor()
        }
    }

    MyButton { // our custom button
        id: rndButton
        text: "PICK"
        onClicked: function() {
            var value = Math.random();
            rndText.text = value.toFixed(rndButton.text.length - 2);
            colorPicker.encodedColor = value;
        }
    }
}

Collecting it altogether, our main.cpp would just need to create the myApp object and use that as the main UI element:

把它收集起来,我们的main.cpp只需要创建myApp对象并将其用作主UI元素:


// include usual Qt headers, etc.

#include "myapp.h" // generated C++ header (for myApp.qml)

int main(int argc, char *argv[]) {
    QGuiApplication app(argc, argv);

    QQmlEngine e;
    QQuickWindow window;

    myApp compiledQmlApp(&e);
    compiledQmlApp->setParentItem(window.contentItem());

    window.show();
    return app.exec();
}

Result

结果

With successful compilation of the pieces above, we should now have a usable QML application. We have intentionally skipped some boilerplate parts (centering the UI within a window, spacing and padding elements, choosing color scheme for the app, #include-ing and import-ing things, etc.) as you should clearly know all that if you have been doing some UI / QML programming before. The important stuff to take with you here is the way we order the source code, what ends up being written in CMake, how our C++ class is exposed to QML and, most importantly, how the qmltc-generated class can be used. As the last bit, you can also observe (a slightly more polished) result in the end:

成功地编译了上述内容之后,我们现在应该有了一个可用的QML应用程序。我们故意跳过了一些样板部分(将UI集中在窗口中,间隔和填充元素,为应用选择颜色方案,#包括填充和导入内容等),因为如果您以前做过一些UI/QML编程,您应该清楚地知道这一切。这里要考虑的重要问题是我们如何对源代码进行排序,最后写在CMake中,我们的C++类是如何暴露于QML的,并且最重要的是如何使用qmltc生成类。作为最后一点,您还可以在最后观察(稍微更精细的)结果:

The way forward

展望

As stated in the beginning, the QML type compiler is currently in the Technology Preview phase. Despite this, we want to show it to the public already to collect useful feedback, to learn about the ways you interact with the compiler, what challenges you have and which improvements (and in which areas) would be most relevant. The main resource to start with would be the documentation of qmltc as well as the more general information about QML and tooling (such as qmllint).

​如前所述,QML类型编译器目前处于技术预览阶段。尽管如此,我们还是想向公众展示它,以收集有用的反馈,了解与编译器交互的方式,面临的挑战以及最相关的改进(以及在哪些方面)。首先,主要的资源是qmltc的文档,以及关于QML和工具(例如qmlint)的更一般的信息。

As usual, you are welcome to participate through the various channels (with the ones advertised on The Qt Project page as the more visible to us) or submit bugs, suggestions, feature proposals through our bugtracker.

​像往常一样,欢迎您通过各种渠道参与(我们更容易看到Qt项目页面上的广告),或者通过我们的bugtracker提交bug、建议和功能建议。

And, of course, do not hesitate to read more stuff on The Qt Company blog as we try to keep you (yes you!) up to date.

​当然,当我们试图留住你(是的,你!)时,不要犹豫,在Qt公司的博客上读更多的东西最新的

你可能感兴趣的:(QtBlog,qt)