深度解析qml引擎---(1)Qml文件加载

                                                                    “美的事物是永恒的喜悦” --- 济慈
                                                                      深度解析qml引擎---(1)Qml文件加载_第1张图片


对于qml引擎的解读,该系列总共有四篇文章。文章内容参考了国外的相关系列文章 QML Engine Internals。

该系列博文都是基于qt5的QtQuick2.0。

每一个qml基本类型都对应了一个C++类,当你写的qml文件被加载时,qml引擎最终会为文件中的每个基本类型创建一个C++类对象,这些对象被按照树的结构组织起来。关于qt qml的基本用法,qt官网上有说明http://doc.qt.io/qt-5/qtqml-index.html, 不熟悉的人可以参照学习。

以下面的例子作为说明:

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()
        }
    }
}

 

该文件包含了三个基本的qml基本元素, Rectangle, Text and MouseArea. 分别对应C++类: QQuickRectangle, QQuickText and QQuickMouseArea. 这些类对qt用户来说是不可见的。 这些元素最终会被qt的内部机制通过调用Opengl来绘制出来,绘制和事件处理的都是由QQuickWindow类来进行管理的,例如,如果系统由专门的渲染线程的话,该类负责与该线程进行交互。

可以借助 KDAB’s Qt introspection tool, Gammaray,来查看针对qml文件生成的c++对象树,例如上面的qml文件对应的对象树如下所示:

深度解析qml引擎---(1)Qml文件加载_第2张图片

上图中类QQuickMouseArea 和QQuickText 按照预期出现在了树中,但是,类QQuickRectangle_QML_0是什么呢? 在qt中没有这个c++类,在后续的文章中会给出解释的,这里暂且将其看作是类QQuickRectangle 。

用QML profiler来分析加载该qml文件的程序,得到:

深度解析qml引擎---(1)Qml文件加载_第3张图片

从上图可以看出,场景绘制的过程中,Creating 和 painting阶段花费了一些时间。其中的compiling阶段具体是在干什么呢,这就需要仔细研究qml文件被qml引擎加载的过程了。

加载QML文件的步骤:

1)Parsing     2)Compiling  3)Creating

下面分别进行介绍:


1) Parsing阶段:

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解析,通过语法解析后,会建立一个abstract syntax tree(AST),即抽象语法树,对于上面的qml文件,对应的语法树如下:

深度解析qml引擎---(1)Qml文件加载_第4张图片

这个AST是相当底层的,了解一下即可。然后,该语法树会被一个visitor进行遍历,将其转换成一个较为高层的数据结构,该数据结构包含 Objects, Properties ,Values, 其中Objects对应QML元素,property/value对应的是 QML中的属性/值,例如color属性的取值为lightblue,另外,信号和信号对应的槽函数亦可以看作是属性/值,例如 onClicked信号及其对应的槽函数(Javascript function)。

2) Compiling  阶段:

至此,得到 Objects, Properties ,Values 结构之后,这些信息对于创建相应的C++类对象并为对象的属性赋值 已经足够了,但是,为了提高效利,qml引擎并不会直接用这些数据来建立c++对象,而是先用对这些数据进行处理,并生成 QQmlCompiledData object ,这个过程就是compiling阶段,对应QML profiler中的compiling阶段!!  之所以有这个过程,是因为使用 QQmlCompiledData 来建立c++对象更快。 例如,有一个Button.qml文件,该文件会经常被其他的qml文件使用,这个文件会仅仅被compile一次,生成的QQmlCompiledData会被保存,每当Button被使用的时候,只需读取这份被保存的数据来创建一个c++对象即可。

To sum up: Parsing a QML file and compiling it is only done once, after that the QQmlCompiledData object is used to quickly create the C++ objects. The next step is creating.

3) Creating阶段:

这里不对QQmlCompiledData进行分析,但是QQmlCompiledData中的一个成员是值得提到的: “QByteArray bytecode” 。bytecode中包含了关键的信息:建立c++对象的说明,为对象的属性附上什么值。bytecode之外的其他成员仅仅起到辅助作用。

在creating阶段,class QQmlVME负责解析包含了大量关键信息的bytecode,其作用相当于一个interpreter。阅读QQmlVME::run(), 函数可以发现该interpreter遍历bytecode中包含的所有instructions,对每一种instructions都会有像一个的处理分支。 在运行app是令QML_COMPILER_DUMP=1,我们可以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 是最重要的一个instruction,它创建一个c++对象(using a database of registered objects in QQmlMetaType.)

STORE_INTEGER 对应的是将一个整数值赋给一个property。
STORE_SIGNAL   对应 create a bound signal handler.
STORE_*_BINDING 对应 create a property binding. 更多关于binding的介绍在后续的博客中。
SETID obviously sets the identifier of an object, which is not an ordinary property.

对bytecode进行解释的VME解释器 维护一个存放 objects的栈,STORE_* 这种指令操作的是位于栈顶的object。 FETCH 指令将一个特定的QObject放在栈顶,POP指令用来移除栈顶的object。 所有的instructions都大量使用了integer indices,“例如, the STORE_COLOR instruction writes to property 41, which is the property index of the target QObject’s meta object.”

To sum up: Once a QML file is compiled, creating an instance of it is just a matter of executing the bytecode of the compiled data.

总结:

这篇文章讲了 加载qml文件过程中的parse, compile, creating阶段。下一文章将会介绍property binding的过程。

 


Ref:

https://www.jianshu.com/p/3e959cbaff3a

http://www.kdab.com/qml-engine-internals-part-1-qml-file-loading/

转载于:https://www.cnblogs.com/butterflybay/p/10347862.html

你可能感兴趣的:(深度解析qml引擎---(1)Qml文件加载)