译者注:这个解析QML引擎的文章共4篇,分析很是透彻,在国内几乎没有找到相似的分析,为了便于国内的QT/QML爱好者和工做者也能更好的学习和理解QML引擎,故将这个系列的4篇文章翻译过来。翻译并非彻底直译,有不足之处,请指正,谢谢!html
———————————————————————————————————————————git
在这个系列的博文中,咱们将深刻探寻隐藏在QML引擎背后的那些鲜为人知的玄机,一步步揭晓它内部实现的原理。这些博文都是基于Qt5版本的QtQuick,QtQuick 2.0来深刻分析的。函数
众所周知,QML文件中每一个元素都对应于一个C++类。QML引擎在加载QML文件时,会为文件中的全部元素以某种方式建立相应的C++对象。在这篇博文中,咱们将探寻QML引擎从解析QML文件开始,到造成一棵完整的C++对象树的整个过程。Qt官方文档已经大篇幅地阐述了QML和C++是如何协同工做的,这些内容值得你花上一些时间评鉴一番。在这个系列的博文中,我假设你已经对该官方文档有所了解。工具
首先咱们将使用一个不怎么有用,也不怎么使人感到兴奋,但却能体现QML有趣的地方的例子:学习
上面这个QML文件包含三个元素:Rectangle(矩形)、Text(文本)和MouseArea(鼠标区域)。这些元素分别对应于C++类:QQuickRectangle、 QQuickText和QQuickMouseArea。这些类只被导出到QML中,在C++版本中它们是私有的,不能被Qt用户使用。这些元素将被绘制在一个OpenGL scenegraph中,绘制及事件处理都是由QQuickView控制的。咱们能够利用KDAB的Qt自检工具GammaRay来验证QML文件对应的C++对象树:ui
和咱们预想的同样,QQuickMouseArea和QQuickText类显示在对象树中。但QQuickRectangle_QML_0又是什么呢?在Qt的源代码中压根没有同名的C++类! 这个问题咱们会在后续的博文中解答,你能够暂时假设它就是QQuickRectangle类型的一个对象。翻译
让咱们更进一步,用QML分析器(QML profiler)来运行并分析这个例子程序:code
如上图所示,在场景设置过程当中,执行了少量的绘制,这和咱们预想的同样。后续的建立阶段花费了大量的时间。但还有一个编译阶段,编译阶段是个啥啊? 都干些什么事?是在建立机器码吗?看来是时候更深刻一点地分析加载QML文件的代码了。orm
当加载QML文件时,会执行三个不一样的步骤,接下来咱们将深刻研究这些步骤:
1.解析
2.编译
3.建立
首先,QML文件是由QQmlScript::Parser这个解析器来解析的。该解析器内部的绝大多数内容都是由��语法文件自动生成的。咱们这个例子的抽象语法树(AST)看起来是这样的:
这个AST是比较底层的东西,紧接着,它将被转换成更高层级结构的对象,属性和值。这是经过使用一个访问器遍历AST来完成的。这一步的对象就和QML中的元素一一对应上了,且对象的属性/值和QML元素的属性/值也一一对应上。咱们的例子中Rectangle元素的属性“color”,其对应的值是“lightsteelblue”,它们就是属性/值的关系。即便像onClicked这样的信号处理程序也被看做只是属性/值的关系,属性是onClicked,值就是JavaScript函数体。
在理论上,对象,属性和值已经足够用于建立对应的C++对象,并给属性赋上对应的值。但这些对象,属性和值依然过于原始,在建立C++对象以前,还须要进行一些后置处理。这些后置处理是由QQmlCompiler来完成的,这对应于QML分析器(QML profiler)输出中看到的编译阶段。该编译器会为QML文件建立了一个QQmlCompiledData对象。 用QQmlCompiledData建立C++对象比直接使用对象、属性和值来建立C++对象快了不少。当屡次使用同一个QML文件,该文件也只会编译一次。好比在一个工程中,其余全部的QML文件都会用到的Button.qml,编译时Button.qml只会被编译一次。Button.qml的QQmlCompiledData会一直保存,每次使用该按钮组件时,都会根据这个Button.qml的QQmlCompiledData来建立C++对象。在编译以后,就是建立阶段,这在QML分析器(QML profiler)的输出中能够看到。
综上所述:解析和编译QML文件都只会作一次,在此以后,都是直接使用QQmlCompiledData对象来快速建立C++对象。
我不会深刻研究QQmlCompiledData的细节,但有一个东西可能会引发你的注意:“QByteArray bytecode”成员变量。实际上,建立C++对象并给它的属性赋值的指令会被编译为了字节码,以后由字节码解析器解析!字节码包含了一堆指令,当这些指令执行时,QQmlCompiledData的其他部分仅是辅助数据。
在建立阶段,字节码是由QQmlVME类解析的。阅读QQmlVME::run()这个函数的代码,里面有一个循环用于遍历字节码包含的全部指令,在循环体内部,有一个很大的断定不一样指令类型的switch语句。运行带有QML_COMPILER_DUMP=1的例子程序,咱们能够看到字节码所包含的每一个指令:
CREATE_SIMPLE 指令是最重要的,它会建立一个C++对象,而后注册到QQmlMetaType中的一个用于注册对象的数据库。
STORE_INTEGER 指令为属性赋一个整数类型值。
STORE_SIGNAL 指令用于建立信号的处理器。
STORE_ * _BINDING 指令用于建立一个属性的绑定。更多关于绑定的内容会在这个系列的下一篇博文中说明。
SETID 指令设置一个对象的标识(id),它不是一个普通的属性。
VME有一个对象栈,STORE_*的全部指令都操做栈顶对象,FETCH指令在堆顶放置一个特定对象,POP指令会移除顶部对象。全部指令都大量使用整数索引,例如STORE_COLOR指令写入属性41,41就是目标对象的元对象的属性索引。
综上所述:一旦一个QML文件编译完成,建立它的实例就只和编译后的字节码的执行有关。
在这篇博文的最后,咱们已经揭示了一个QML文件是如何进行解析、处理、编译的,以及VME是如何建立对象的。我但愿你已经更加深刻地理解了QML引擎。
下一篇的博文将进一步探讨属性绑定是如何进行的,敬请关注!