Les entrailles du moteur QML

Première partie : le chargement des fichiers

Thomas McGuire

Traducteur

Arnold Dumas

Cette série d'articles explorera les entrailles de QML et les mécaniques internes de son moteur d'exécution. Ces articles reposent sur la version QtQuick 2.0, disponible avec Qt 5.0.

Commentez

- Titre : Les entrailles du moteur QML

- Auteur : Thomas McGuire

- Traducteur : Arnold Dumas

- Parution : 30 juin 2013

- Mise à jour : 13 septembre 2013

- Licence : Copyright ® 2013 Thomas McGuire. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Article original

- II. Introduction

- III. L'exemple

- IV. Étapes du chargement d'un fichier QML

- IV-A. Analyse

- IV-B. Compilation

- IV-C. Création

- V. Conclusion

- VI. Remerciements

I - Article original

Le blog KDAB est rédigé par les ingénieurs de KDAB s'occupant des formations, de la consultance ainsi que du développement (de Qt et de produits additionnels). Vous pouvez trouver les versions originales.

Cet article est une traduction de l'article original écrit par Thomas McGuire paru le 27 juillet 2012.

Cet article est une traduction de l'un des articles en anglais écrits par KDAB. Les éventuels problèmes résultant d'une mauvaise traduction ne sont pas imputables à KDAB.

II - Introduction

La plupart des gens savent que chaque élément d'un fichier QML est, en pratique, transformé en un objet C++. Quand un fichier QML est chargé, le moteur QML crée d'une façon ou d'une autre un objet en C++ pour chaque élément contenu dans ledit fichier. Cet article a pour objectif de présenter la façon dont le moteur QML part d'un fichier texte pour aboutir à un arbre d'objets au sens C++ du terme. La documentation Qt à propos de QML contient une description détaillée sur la façon dont QML et C++ cohabitent. Cette partie vaut vraiment la peine d'être lue. Dans cette série d'articles, les notions présentées dans la documentation seront considérées comme acquises.

III - L'exemple

L'article utilisera, tout le long de son développement, le même code d'exemple, qui ne fait certes rien de transcendant, mais qui utilise des fonctionnalités de QML intéressantes à étudier :

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

Ce fichier contient trois éléments : un Rectangle, un Text et une MouseArea. Ces éléments correspondent respectivement aux classes C++ QQuickRectangle, QQuickText et QQuickMouseArea. Ces classes sont seulement disponibles à travers QML, leurs versions C++ restent privées et ne sont pas disponibles pour les utilisateurs de Qt. Les éléments sont affichés à l'aide d'un graphe de scène utilisant OpenGL. Le rendu et la gestion des événements sont tous les deux orchestrés par une QQuickView. L'arbre d'objets C++ généré correspond bien à la structure du fichier QML, ce que l'on peut vérifier à l'aide de l'outil d'introspection de KDAB, Gammaray :

Sans surprise, les classes QQuickMouseArea et QQuickText apparaissent dans l'arbre des objets. Mais que fait QQuickRectangle_QML_0 ? Aucune classe C++ ne porte ce nom dans les sources de Qt ! Nous verrons cela dans un prochain article. Pour le moment, considérez simplement que cet objet est un QQuickRectangle.

Allons plus loin et lançons maintenant l'application dans le profileur QML :

Une bonne partie du temps est dédiée à mettre en place la scène, ce qui constitue l'étape de création, étape suivie par un peu de rendu, chose à laquelle on pouvait s'attendre. Mais qu'est-ce que cette phase appelée compilation ? S'agit-il de la génération de code machine ? Ou alors est-ce autre chose ? Pour le savoir, il est temps de s'intéresser au chargement d'un fichier QML par le moteur d'exécution QML.

IV - Étapes du chargement d'un fichier QML

Le chargement d'un fichier QML se fait en trois étapes bien distinctes, qui seront détaillées dans les prochaines parties :

1. Analyse ;

2. Compilation ;

3. Création.

IV-A - Analyse

Dans un premier temps, le fichier QML est lu et analysé. Cette dernière tâche est accomplie par la classe QQmlScript::Parser. La plupart des rouages de l'analyseur sont automatiquement générés par un fichier décrivant une grammaire. L'arbre syntaxique abstrait (AST, pour abstract syntaxic tree) correspondant à l'exemple se trouve ci-dessous :

(Cette image a été générée en utilisant une version corrigée de graphviz, correctif disponible à cette adresse: http://www.kdab.com/~thomas/stuff/ast-graph.diff)

L'AST est plutôt bas niveau, c'est pourquoi il est transformé en une structure de données de plus haut niveau composé d'objets, de propriétés et de valeurs lors de la prochaine étape. Cela est fait en utilisant un visiteur qui parcourt l'arbre syntaxique abstrait. À ce niveau, les objets correspondent aux éléments QML, et les propriétés / valeurs correspondent à leurs homologues QML, « color » et « lightsteelblue » par exemple. Même les gestionnaires de signaux, à l'instar de onClicked, sont en fait de simples paires propriétés / valeurs ; dans ce cas, la valeur est le corps de la fonction JavaScript.

IV-B - Compilation

On en sait maintenant assez sur la structure des objets, des propriétés et valeurs pour étudier la création de classes C++ associées. Quoi qu'il en soit, les objets, propriétés et valeurs sont toujours bruts de décoffrage et nécessitent une finition avant de créer des objets C++. Cette étape de finition est effectuée par QQmlCompiler, soit visible dans le profileur QML.

Le compilateur crée un objet de type QQmlCompiledData pour le fichier QML afin de créer des objets C++. C'est bien plus rapide que de le faire à partir des objets, propriétés et valeurs. Lorsqu'on utilise un fichier QML à plusieurs reprises par exemple, Button.qml, qui est utilisé dans d'autres fichiers QML ne sera compilé qu'une seule fois. Les données sous forme de QQmlCompiledData pour Button.qml seront gardées sous le coude, et réutilisées à chaque fois qu'un nouveau Button sera déclaré dans du code QML. Ensuite vient la phase de création, qui peut être vue dans la sortie du profileur QML.

Petit résumé : l'analyse et la compilation d'un fichier QML ne sont faites qu'une seule fois chacune, ainsi, il est possible par la suite de créer rapidement des objets C++ à partir de l'objet QQmlCompiledData.

IV-C - Création

Inutile de rentrer dans les détails de QQmlCompiledData mais une chose a dû retenir votre attention : l'attribut bytecode de type QByteArray. Les instructions pour créer les objets C++ et pour assigner les valeurs aux propriétés sont compilées en code intermédiaire, code qui sera ensuite interprété. Ce code intermédiaire contient un amas d'instructions. Le reste du QQmlCompiledData est formé de données auxiliaires qui seront utilisées à l'exécution du programme.

Dans cette phase de création, le code intermédiaire est interprété par la classe QQmlVME. Si l'on regarde l'implémentation de la méthode QQmlVME::run(), on se rend compte que l'interpréteur se contente de se balader dans toutes les instructions contenues dans ce code intermédiaire avec une grosse structure de contrôle de type « switch ». En exécutant l'application avec QML_COMPILER_DUMP=1, on peut voir les différentes instructions contenues dans le code intermédiaire :

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 est l'instruction la plus importante, elle crée un objet C++ en utilisant une base de données composée des objets enregistrés dans QQmlMetaType ;

- STORE_INTEGER est l'instruction écrivant une valeur entière dans une propriété ;

- STORE_SIGNAL est utilisée pour créer un gestionnaire de signal lié ;

- STORE_*_BINDING est utilisée pour créer une liaison sur une propriété, approfondi dans le prochain article de la série ;

- SETID sert évidemment pour définir l'identifiant d'un objet, qui n'est pas une propriété ordinaire.

La VME contient une pile d'objets. Toutes les instructions de la forme STORE_* travaillent sur l'objet qui se trouve au sommet de cette pile. L'instruction FETCH positionne explicitement un QObject sur le sommet de la pile, et POP supprime l'objet en haut de cette pile. Toutes ces instructions ont massivement recours à des indices sous la forme d'entier : par exemple, l'instruction STORE_COLOR écrit sur la propriété numéro 41, ciblée au sein du QObject.

Pour résumer : une fois que le fichier QML est compilé, créer son instance revient juste à exécuter le code intermédiaire des données compilées.

V - Conclusion

C'est ici que se termine cet article, qui a parcouru un bon bout de chemin sur la façon dont un fichier QML est lu, traité et compilé pour finalement donner étudier la manière dont les objets sont réellement créés par la VME. J'espère que vous avez maintenant une meilleure idée des rouages du moteur QML.

Dans le prochain article, il sera question du fonctionnement des liaisons de propriétés.

VI - Remerciements

Au nom de toute l'équipe Qt, j'aimerais adresser le plus grand remerciement à KDAB pour nous avoir autorisés à traduire cet article !

Je tiens à remercier Thibaut Cuvelier pour ses conseils et zoom61 pour sa relecture.