原文地址:http://www.kdab.com/qml-engine-internals-part-1-qml-file-loading/
在这一系列的blog文章里,我们将探索QML引擎,并且揭开其内部的工作机制。这些文章内容都是基于Qt5的QtQuick2.0版本。
大多数人已经知道QML中各个元素背后其实是C++类。当一个QML文件载入时,QML引擎将根据文件中的所有元素,以某种机制来生产一个C++对象。在这篇文章里,我们解释这个过程,知道QML引擎是如何阅读一个基于文本的文件来生成一个完整的C++对象树。在Qt的文档中,已经详细解释了QML是如何与C++一起工作,这些文档非常值得花时间去阅读。在这个blog文章系列里,我假设你已经阅读过了这些文档并且都明白了。
例子
我们将使用一个例子贯穿全文,这个例子并不具有实际的用处或者激动的地方,但是覆盖了几个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 has three elements in it, Rectangle, Text and MouseArea. These correspond to the C++ classes QQuickRectangle, QQuickText and QQuickMouseArea. Those classes are only exported to QML, the C++ versions are private and not available for users of Qt. The elements are drawn using a OpenGL scenegraph. Drawing and event handling are both orchestrated byQQuickView . The C++ object tree corresponds to the one in the QML file, which we can verify with KDAB’s Qt introspection tool, Gammaray:
这个QML文件里有三个元素,分别是Rectangle,Text和MouseArea。 它们分别对应C++类中的QQuickRectangle, QQuickText 和 QQuickMouseArea。这些类只暴露给了QML,其C++版本是私有的,并且Qt的使用者们无法使用。这些元素的绘制都使用了一个OpenGL scenegrap。绘制和事件的处理则都是由QQuickView负责协调。下面的C++对象树对应了一个QML文件,可以通过使用KDAB的Qt introspection tool, Gammaray来验证:

As expected, the classes QQuickMouseArea and QQuickText show up in the object tree. But what is QQuickRectangle_QML_0? There is no C++ class with such a name in the Qt sources! We’ll come back to that in a later blog post, for now you can assume that an object of type QQuickRectangle was used.
Let’s go a step further and run the application in the QML profiler:
正如预测的,类QQuickMouseArea和QQuickText都在对象树显示了,但是QQuickRectangle_QML_0是啥呢?在Qt源代码里并没有这样的C++类名字。不过我们将在稍后的blog文章了解释这些,现在我们只需知道,一个QQuickRectangle的对象的确使用到了。
让我们接着下一步,在QML profiler运行这个程序:

We see that a bunch of time is spent setting up the scene, which is the Creating phase. This is followed by a bit of Painting, which is what we would expect. But what is this Compiling phase? Is it creating machine code? Time to dig into the QML file loading code a bit deeper.
我们可以看到一堆时间花费在设置场景,这对应的是创建阶段。接下来的一点则是绘制阶段,就是我们预想将要出现的画面。但是编译阶段是什么呢?难道是创建机器码的步骤?需要花费我们一定时间来深入QML文件的载入步骤,因为这些过程相对来说已经比较深入了。
QML文件载入步骤
When loading a QML file, there are 3 distinct steps happening, which we’ll examine in the following sections:
当载入一个QML文件时候,这里将会有三个明确的步骤发生,这将是我们下面章节将要解释的:
1.解析阶段
2.编辑阶段
3.创建阶段
解析阶段
First of all, the QML file is parsed, which is handled by QQmlScript::Parser. Most of the parser internals are auto-generated from a grammar file. The abstract syntax tree (AST) of our example looks like this:
首先,QML文件要被解析,这个过程是由QQmlScript::Parser来完成。大多数内部将是将自动从grammar file中生成。本文里,例子的抽象标记叔(AST)看过去是这样的:

(这图生成的工具graphviz使用了一个位图生成器的补丁,其补丁在http://www.kdab.com/~thomas/stuff/ast-graph.diff)
这个对象标记树是十分底层的,在下个步骤才转换到更高层次的结构如Objects(对象),Properties(属性) and Values(值)。这一切是使用AST的visitor(观察者,探索器)完成的。在这一层,对象代表了QML元素,属性/值的配置代表了QML属性与值,例如”color”属性,被赋值了”lightsteelblue“。甚至信号处理如onClicked也是属性/值的配对,在这个情况下,值代表了Javascript的函数体。
编译阶段
现在理论上,Objects(对象集合), Properties(属性集合)以及Values(值集合)的结构已经足够解释如何创建C++对象以及属性值类之间的关联。尽管如此,Objects(对象集合), Properties(属性集合)以及Values(值集合)仍旧是十分原始,并且需要在C++对象集合生成前进行一些后续处理。这些后续处理主要是由QQmlCompiler完成,这解释了QML profiler(QML性能检测器)中编译阶段所展示的。这个编译过将针对这个QML文件生成一个QQmlCompiledData。审查QQmlCompiledData并且生成C++对象的过程,其速度远胜于审查Objects(对象集合), Properties(属性集合)以及Values(值集合)。当使用一个QML文件多次后,假设是例子是一个Button.qml并且使用了在其他QML文件里的各个地方,那么 Button.qml将只会被编译一次。Buttom.qml的QQmlCompiledData将会保持,当每次Buttom组件使用时,C++对象将会由QQmlCompiledData来审查。在后续的创建阶段,可以通过QML性能监测器看到输出。
简单地总结下:QML文件的解析和编译将会只执行一次,接下去QQmlCompiledData对象将会被使用快速创建C++对象。
创建阶段
笔者不想对QQmlCompiledData的细节做过度深入,但是有一样东西可以能吸引你的注意力:QByteArray字节码(QByteArray bytecode)成员变量。该指令用来创建C++对象,分配正确的值到对应属性,之后由字节码解释器编译到字节码去。这些字节码包含了一串的指令,并且当指令执行时候QQmlCompiledData的剩余部分将作为辅助的数据使用。
在创建阶段,字节码的解释是由QQmlVME。查看QQmlVME::run(),解释器简单地循环了所有字节码里的指令,并且是由一个庞大的switc状态正对不通的指令说明类型。在运行程序时,通过设置QML_COMPILER_DUMP=1,我们可以看到独立的字节码说明:
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是最重要的指令,它产生C++对象,使用QQmlMetaType中注册对象的数据库。
STORE_INTEGER指令用来向一个属性写入整型。
STORE_SIGNAL 指令用来处理一个约束的信号处理者。
STORE_*_BINDING 指令用来处理生成属性绑定。更多关于绑定的内容将会在下一篇blog文章内提到。
SETID 显然是用来设置一个对象的识别,这可不是一个普通的属性。
VME有一个对象集合的站,所有类似STORE_*操作的指令都在顶部对象。FFTCH指令将特定的对象放置到栈顶,POP指令则是移植栈顶的对象。所有的指令都大量使用了整数指数,比如 STORE_COLOR指令写入属性41,代表了在这个目标元数据对象中的属性索引。
简单总结下:一旦QML文件解析完毕,创建实例的过程就仅是是执行编译数据字节码的过程事务。
结论
在这篇文章结尾,我们已经知道了QML文件时如何解析,处理,编译以及稍后对象时如何通过VME生成的。我希望你能从QML引擎的这些知识中获得许多有趣的见解。
请等待下一篇文章,在那里我们解释属性绑定是如何工作的。