譯者注:這個解析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引擎。
下一篇的博文將進一步探討屬性綁定是如何進行的,敬請關注!