In
this (
part 2 in translation ) series of articles, we will look under the hood of the QML engine and reveal some of the features of its internal work. The articles are based on Qt5 version of QtQuick, QtQuick 2.0.
Most people know that every element in a QML file relies on a particular C ++ class. When the QML file is loaded, the QML engine somehow creates one C ++ object for all the elements in the file. In this post, we will look at how the QML engine proceeds from reading a text file that includes the full tree of C ++ objects. The Qt documentation has a
section with an extensive description of the interaction between QML and C ++, which is worth the time spent reading. In this series of articles, I assume that the user has read and understands what is described in the documentation.
Example
In this article we will use an example that does nothing special, but includes some interesting parts of QML:
import QtQuick 2.0 Rectangle { id: root width: 360 height: width + 50 color: "lightsteelblue" property alias myWidth: root.width property int counter: 1 function reactToClick() { root.counter++ } Text { id: text text: qsTr("Hello World: " + counter) anchors.centerIn: parent } MouseArea { id: mouseArea anchors.fill: parent onClicked: { reactToClick() } } }
This file consists of three elements: Rectangle, Text and MouseArea. They correspond to the C ++
QQuickRectangle ,
QQuickText, and
QQuickMouseArea classes . In addition, these classes are only exported to QML, are private and inaccessible to Qt users directly from C ++. Elements are drawn on the screen via OpenGL using the
QML Scenegraph (QML scene tree). The order in which events are
drawn and processed is determined by
QQuickView . The one-to-one QML file corresponds to the generated C ++ object tree, which can be checked using the tool developed by the Qt KDAB command, called
Gammaray .
')

As expected, the QQuickMouseArea and QQuickText classes are displayed in the object tree. But what is a QQuickRectangle_QML_0? Such a C ++ class simply does not exist in the Qt source! We will return to this in later posts, but for the time being we will simply assume that the QQuickRectangle object was used.
Go ahead and run the application in
QML Profiler :

As we can see, the lion's share of time is spent on the creating phase. And a little bit at the drawing stage, the result of which we see on the screen. But what is the compiling phase? Is it meant to create machine or bytecode? Let's take a look at the file download algorithm a little deeper.
Steps for loading a QML file
The loading of a QML file can be divided into 3 independent steps, which we will consider in the following sections:
- Parsing (Analysis)
- Compiling (Compilation)
- Creating
Parsing
First of all, the QML file is parsed using QQmlScript :: Parser. Most of the internal rules of the parser are generated automatically from the grammar file. The abstract syntax tree (AST) from our example will look like this:

(The picture was generated using graphviz and this
patch )
Initially, AST works at a rather low level and at the next step of its work, at a higher level, it turns into structures of
objects (Objects) ,
properties (Properties) and
values ​​(Values) .
AST :: Visitor is doing this. At this level, Objects correspond to QML elements, and property / value pairs (Property / Value), such as “color” and “lightsteelblue” correspond to the properties and values ​​of these QML elements. Even signal handlers like onClicked at this stage are just property / value pairs, in this case the value is the body of the Javascript function.
Compilation
In theory, having the structure of objects, properties and values, we can already create C ++ objects associated with these QML elements and assign them appropriate values. However, the objects, properties, and values ​​are still fairly raw, and require some post-processing before C ++ objects can be created. Post-processing is carried out by the
QQmlCompiler object, which explains what is meant by the “Compilation” stage in our profiler. The compiler creates a QQmlCompiledData object for the specified QML file.
Working with
QQmlCompiledData and creating C ++ objects from it is much faster than working directly with objects, properties, and values. As mentioned above, a tree of objects is built for each individual QML file. When reusing a QML file, for example, Button.qml, which is commonly used in other QML files, a QQmlCompiledData object will be created for it only once, which will be stored in memory and used to create a C ++ object each time it encounters a Button .qml during the parsing application. After this comes the stage of creation, which we saw in the profiler window.
To summarize: Each QML file is parsed and compiled only once, after which the QQmlCompiledData object is used to quickly create C ++ objects.
Creature
I will not go into details about QQmlCompiledData, but one thing could get your attention: the member variable "QByteArray bytecode". The instructions for creating C ++ objects and the correct assignment of values ​​to its properties are compiled as bytecode, which is then interpreted by the bytecode interpreter (
from the translator: the interpreter interpreted and interpreted, but not ... =) ). The bytecode contains a set of instructions, and the rest of the content in QQmlCompiledData is used only as auxiliary data when executing the instruction.
At the creation stage, the bytecode is interpreted by the
QQmlVME class. The interpreter's QQmlVME :: run () function sequentially executes bytecode instructions based on a large switch statement. When launching the application with the QML_COMPILER_DUMP = 1 flag, we can see separate bytecode instructions:
Index Operation Data1 Data2 Data3 Comments ------------------------------------------------------------------------------- 0 INIT 4 3 0 0 1 INIT_V8_BINDING 0 17 2 CREATECPP 0 3 STORE_META 4 SETID 0 "root" 5 BEGIN 16 6 STORE_INTEGER 45 1 7 STORE_COLOR 41 "ffb0c4de" 8 STORE_COMPILED_BINDING 10 2 0 9 STORE_DOUBLE 9 360 10 FETCH_QLIST 2 11 CREATE_SIMPLE 32 12 SETID 1 "text" 13 BEGIN 16 14 STORE_V8_BINDING 43 0 0 15 FETCH 19 16 STORE_COMPILED_BINDING 17 1 1 17 POP 18 STORE_OBJECT_QLIST 19 CREATE_SIMPLE 32 20 SETID 2 "mouseArea" 21 BEGIN 16 22 STORE_SIGNAL 42 2 23 FETCH 19 24 STORE_COMPILED_BINDING 16 0 1 25 POP 26 STORE_OBJECT_QLIST 27 POP_QLIST 28 SET_DEFAULT 29 DONE -------------------------------------------------------------------------------
- CREATE_SIMPLE is the most important instruction; it creates a C ++ object using a database of registered objects: QQmlMetaType .
- STORE_INTEGER - an instruction for assigning an integer value to a property.
- STORE_SIGNAL is used to create an associated signal handler.
- STORE_ * _BINDING is used to create property bindings. We will talk more about bindings in the next post of this series.
- SETID obviously sets the object identifier, which is not a regular property.
VME has a stack of objects, all instructions of type STORE_ * work with an object located at the top level. FETCH places a specific QObject at the top of the stack, POP removes the top object. All instructions make extensive use of numeric indices, for example, the STORE_COLOR instruction writes to property 41, which is the index of the object.dinary meta-property of the target QObject.
To summarize: After the QML file is compiled, creating an instance of its C ++ object is just a matter of executing the bytecode generated from the transferred compiled data.
Conclusion
At the end of this note, we learned how to understand, process and compile QML files. And then how objects are created using VME. I hope you learned something interesting about the QML engine.
Stay with us until the next post, in which we will look at how Bindings work in QML.
From the translator: Continue me in the same spirit or stop? Judging by the modest number of comments to previous translations , the topic is not very interesting for the Habr audience.
PS: And yes, I know that the translator from me is so-so, but I try as best I can =) Errors and comments - I ask in PM.