• Aucun résultat trouvé

2.4 Système de composants pour la réalité augmentée

2.4.1 Le composant

Le composant est l’élément central de l’application. D’après les définitions que nous avons donné, ce dernier renferme du code compilé, doit pouvoir fournir une interface et est soumis à une ou plu-sieurs lois de composition entre les divers composants. De ces trois propriétés fondamentales dé-coulent les autres. Ceci veut dire que l’on devra rapidement aborder l’aspect implémentation des composants. De plus, certains choix en matière de programmation doivent être effectués très tôt. 2.4.1.1 Choix d’implémentation

Choix du langage de programmation La première décision concerne le langage d’implémenta-tion. Nous avons opté pour le langage C++ qui permet de profiter des avantages de la programmation objet. De plus, comme les programmes écrits dans ce langage passent par une étape de compilation, ceci nous permet d’obtenir du code compilé, qui est la transposition d’un programme écrit en un lan-gage compréhensible par la machine. Ce dernier fonctionne de manière native sur la plate-forme sur laquelle il a été compilé, ce qui est garant de bonnes performances en terme de temps d’exécution.

2.4. SYSTÈME DE COMPOSANTS POUR LA RÉALITÉ AUGMENTÉE 51 Choix du mode de communication C’est le mode de communication inter-composants qui déter-mine également les lois de composition que nous détaillerons ultérieurement. Nous avons opté pour le mécanisme signal-slot décrit en section 2.2.2.3 page 42. Celui-ci est en réalité un appel de procédure déguisé, ce qui garantit la synchronisation des traitements lorsqu’elle est nécessaire.

Choix de la librairie Comme nous ne souhaitions pas ré-implémenter un nouveau mécanisme si-gnal/slot, nous avons choisi une des librairies parmi celles qui implémentent ce paradigme. Notre choix s’est porté sur la librairie QT. Cette dernière présente plusieurs avantages :

– Cette librairie est portable, et fournit une API (Application Programming Interface) qui permet de couvrir bon nombre des fonctionnalités de base des systèmes d’exploitation. A part pour quelques fonctionnalités bas niveau qui sont fortement liées au système d’exploitation, ceci garantit que le code écrit sur une plate-forme peut-être recompilé sur une autre plate-forme sans changements ou presque,

– Le mécanisme signal/slot implémenté permet la connection/déconnection à l’exécution, – Le modèle d’objets implémenté par QT permet de développer certains objets capables

d’auto-introspection, en particulier leur signaux et leurs slots. Or ces derniers constituent l’interface de notre composant. Ces objets seront donc capables d’exporter leur propre interface.

2.4.1.2 Représentation d’un composant

En tenant compte des principes énoncés dans la section précédente, un composant est un objet qui contient des signaux, des slots ainsi que des mécanismes d’introspection permettant d’exporter l’interface de ces signaux et de ces slots. Par la suite, nous nous référerons parfois aux composants en employant le terme objet. Un objet peut donc être représenté sous la forme de la figure 2.5.

signal2(param1) signal1() slot1(param1, param2)

slot2()

slot3(param1)

Object Name : Class Name

FIG. 2.5 – Vue d’un composant avec ses signaux et slots

Il convient de détailler le mécanisme d’implémentation des objets QT qui nous servirons de com-posants par la suite, ainsi que d’expliquer de manière plus concrète le mécanisme signal/slot sous jacent.

2.4.1.3 Implémentation d’un composant

L’implémentation d’un objet (appelé aussi bloc atomique) repose sur un ensemble de contraintes relativement simples que nous allons énumérer au fur et à mesure. Celles-ci seront familières pour quiconque ayant déjà travaillé avec la librairie QT. A ces contraintes liées aux fonctions QT nous n’en rajouterons qu’une. Elle sera liée au constructeur. Voici, pour rappel les différents points qui doivent être respectés lors de l’implémentation d’un objet spécifique à la librairie QT.

La déclaration générale de la classe

Les problèmes d’implémentation liés aux objets est en réalité limité à quelques contraintes sur l’interface employée pour décrire un objet, autrement dit : la déclaration de la classe.

Celle-ci va commencer comme suit :

class NewObject : public QObjectInheritor

Ici NewObject se réfère au nom de la nouvelle classe développée et QObjectInheritor se réfère à tout objet héritant de la classe QObject de l’API QT, y compris QObject. Ainsi, chaque composant doit impérativement hériter de la classe QObject, directement ou indirectement. Bien sûr il ne faudra pas oublier d’inclure auparavant les en-têtes nécessaires à la déclaration de la classe dont nous allons hériter.

Contraintes sur le constructeur

Nous fixons dès à présent la forme du constructeur que nous allons employer pour chacune des classes destinées à être un composant. Le constructeur que chaque composant doit implémenter est le suivant :

public NewObject(QObject* parent, const char* name) ;

Un des constructeurs de la classe doit nécessairement posséder les deux premiers paramètres QObject* parent et const char* name. Si d’autre paramètres suivent, ils doivent impéra-tivement avoir une valeur par défaut en raison du mode d’instanciation des objets qui est utilisé par la suite.

Implémentation des signaux et des slots

Juste après la déclaration de la classe vient une ligne nécessaire pour pouvoir utiliser le mécanisme signal/slot et l’introspection qui est familière aux utilisateurs de QT [Qt, url] :

Q_OBJECT

Le bloc de déclaration des slots commence par : public slots :

La déclaration d’un slot se fait sous la forme :

void nomSlot(typeParam1 param1, typeParam2 param2, ...) ; La seule contrainte existant sur les slots est le type de retour de la fonction qui doit être le mot-clé “void”

De même pour le bloc de déclaration des signaux et la déclaration de ces derniers, nous avons : signals :

void nomSignal(typeParam1 param1, typeParam2 param2, ...) ; Pour le type de retour d’un signal, nous trouvons la même contrainte que pour les slots.

Les paramètres d’un signal donné permettent de passer des valeurs et des données à un slot qui aura le même ordre et le même type de paramètres.

Le reste de la déclaration de la classe ne change pas, ce qui veut dire que tous les concepts connus en C++ peuvent être exploités tels quels pour la déclaration de la classe. La dernière chose qu’il reste à savoir au niveau des objets concerne l’émission d’un signal dans le code de l’objet qui se fait de la manière suivante :

2.4. SYSTÈME DE COMPOSANTS POUR LA RÉALITÉ AUGMENTÉE 53

Exemple d’implémentation

Au final, nous avons inclus dans ce document un exemple de déclaration de deux composants dans le listing 2.1. Ce court exemple répertorie les différents points que nous venons d’énoncer.

# i n c l u d e < q o b j e c t . h> / / En−t ê t e d é f i n i s s a n t l a c l a s s e QObject # i n c l u d e < q s t r i n g . h>

c l a s s Boucle : p u b l i c QObject / / D é c l a r a t i o n de c l a s s e , h é r i t e de QObject {

Q_OBJECT / / U t i l i s é par QT p u b l i c :

/ / C o n s t r u c t e u r aux p a r a m è t r e s f i g é s

Boucle ( QObject ∗ p a r e n t = 0 , c o n s t char ∗ name = 0 ) ; / / D é c l a r a t i o n d e s s l o t s p u b l i c s l o t s : v o i d s e t I t e r a t i o n s ( i n t n ) ; / / D é c l a r a t i o n d e s s i g n a u x s i g n a l s : v o i d n e w I t e r a t i o n ( i n t i ) ; v o i d sendToken ( Q S t r i n g s ) ; } ; c l a s s D i s p l a y I n t : p u b l i c QObject { Q_OBJECT p u b l i c :

D i s p l a y I n t ( QObject ∗ p a r e n t = 0 , c o n s t char ∗ name = 0 ) ; p u b l i c s l o t s :

v o i d d i s p l a y ( i n t i ) ; } ;

Listing 2.1 – Exemple de déclaration de classes représentant des composants

Mécanisme signal/slot

Lorsque l’on utilise un objet QT, le mécanisme signal/slot permet la connection et la déconnection de ces derniers à la volée. La seule contrainte à la connection est que la signature d’un signal doit correspondre à celle du slot auquel on va faire la connection. Cette signature est en réalité la liste des types des paramètres passés.

Une connection s’effectue de la manière suivante, via la méthode de classe implémentée dans

QObject:

connect(objet1, SIGNAL(signal(...)), objet2, SLOT(slot(...))) ; La déconnection s’effectue de la même manière :

disconnect(objet1, SIGNAL(signal(...)), objet2, SLOT(slot(...))) ;

SIGNAL et SLOT sont des macros définies dans l’API QT. Elles prennent pour paramètre une chaîne de caractère donnant le nom et les types des paramètres des signaux et des slots. objet1 et objet2sont des pointeurs vers des objets qui héritent de QObject.

En résumé, si l’on reprend l’exemple déclarant deux composants, si l’on instancie un objet boucle de la classe Boucle et un objet displayint de la classe DisplayInt et si l’on veut connecter le signal void newIteration(int i) de l’objet boucle au slot void display(int i) de l’objet displayint, on écrira :

connect(boucle,SIGNAL(newIteration(int)), displayint,SLOT(display(int))) ;

Le mécanisme signal/slot sera également mis à profit pour pouvoir configurer et initialiser nos composants.

2.4.1.4 Mécanismes d’initialisation

Chaque composant possède à présent des slots. Ces slots peuvent être utilisés pour configurer et initialiser le composant. Les slots qui seront considérés comme étant des slots d’initialisation auront une signature particulière. En effet, pour des raisons de simplicité, ils ne doivent comporter qu’un seul paramètre dont le type fait partie d’une liste pré-définie. Ces types sont les suivants : void (vide) , int (entier sur 32 bits), long (entier sur 64 bits), float (flottant simple précision) , double (flottant double précision), QString (chaîne de caractères) et enfin QObject* (pointeur sur un objet QT).

Par la suite, nous distinguerons deux types d’initialisation :

– les initialisations de pré-connection, qui sont réalisées avant que les composants soient mis en relation les uns avec les autres,

– les initialisations de post-connection, qui sont réalisées après que les composants soient mis en relation les uns avec les autres, ce qui permet aux initialisations de se propager d’un composant à un autre.

La figure 2.6 page ci-contre détaille plus précisément le mécanisme de propagation des initialisations par rapport au type d’initialisation réalisée.

Les composants, une fois crées et compilés sont stockés dans des librairies dynamiques. Toutefois, il faut pouvoir ré-exploiter les différents composants entreposés dans ces librairies lors d’un charge-ment de ces dernières à l’exécution. Pour faciliter cette récupération, ces librairies implécharge-mentent un mécanisme particulier.

2.4.1.5 Librairie dynamique

Une librairie dynamique employée comme un plugin se doit d’avoir des points d’entrée qui per-mettent à cette dernière de communiquer avec le programme principal. Ces points d’entrée sont figés définitivement.

Dans le cas qui nous intéresse, seulement 3 points d’entrée sont à définir pour pouvoir exploiter les objets stockés au sein de cette dernière. Ils sont :

1. une méthode décrivant les objets entreposés dans la librairie,

2. une méthode visant à créer ou instancier un objet de la librairie à la demande, 3. une méthode visant à détruire un objet de la librairie.

Pour faciliter la programmation de ces points d’entrée, nous avons créé un ensemble de trois macros spécifiques déclarées dans un fichier d’en-tête particulier. Le nom du fichier d’en-tête est metalibrarytoolkit.h.

Les macros quant à elles sont :

2.4. SYSTÈME DE COMPOSANTS POUR LA RÉALITÉ AUGMENTÉE 55 Objet A Objet B slotA signalA slotB

Nous supposons qu’un objet A contenant un slot slotA qui active également un signal signalA et qu’un objet B ayant un slot slotB sont présent sur la feuille. Les connections entre composants ne sont pas encore réalisées. Si l’on initialise slotA, celui-ci va activer signalA, qui, n’étant connecté à aucun slot, ne fait rien. (a) Initialisation de pré-connection

Objet A

Objet B slotA

signalA

slotB

Les connections entre composants sont dorénavant réalisées. Si l’on initialise slotA, celui-ci va activer signalA. Ce dernier a été connecté à slotB, qui est alors à sont tour activé. Les initialisations se propagent bien le long des connexions.

(b) Initialisation de post-connection

FIG. 2.6 – Initialisations et mécanisme de propagation des initialisations – METALIB_END qui signale la fin de la librairie,

– METALIB_OBJECT(ClassName) avec ClassName le nom d’une des classes développées. Au final, l’implémentation d’une librairie employant les deux composants dont la déclaration est transcrite dans le listing 2.1 page 53 va receler les lignes écrites dans le listing 2.2.

# i n c l u d e " . . / i n c l u d e / m e t a l i b r a r y t o o l k i t . h " # i n c l u d e " b o u c l e . h " METALIB_BEGIN METALIB_OBJECT ( Boucle ) METALIB_OBJECT ( D i s p l a y I n t ) METALIB_END

Listing 2.2 – Exemple d’implémentation d’une librairie

A présent, nous avons défini l’intégralité de nos composants, de leur interface à la manière dont ils sont stockés et récupérables dans le code compilé. Nous allons maintenant nous intéresser aux lois de composition de nos composants.