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 :

 
Sélectionnez
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 :

Image non disponible

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 :

Image non disponible

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 :

Image non disponible

(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 :

 
Sélectionnez
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.