6 ===========================================================
7 Guide pour le développement d'un module SALOME en C++
8 ===========================================================
10 Ce document a pour objectif de décrire les différentes étapes
11 du développement d'un module SALOME en C++.
12 Il fait suite au document :ref:`pysalome`, qui documente le module PYHELLO, et en reprend la démarche :
13 construction pas à pas d'un module HELLO.
14 Comme de nombreux points ne sont pas repris, il est recommendé de lire ce
15 document préalablement.
18 Les étapes de construction du module exemple
19 ====================================================
20 Le composant choisi pour illustrer le processus de construction en C++
21 est le même que celui choisi pour illustrer la construction du module python :
22 il implémentera donc la même interface idl Corba.
23 Il sera complété par un GUI graphique écrit en Qt.
25 Les différentes étapes du développement seront les suivantes :
27 - créer une arborescence de module
28 - créer un composant SALOME chargeable par un container C++
29 - configurer le module pour que le composant soit connu de SALOME
30 - ajouter un GUI graphique
31 - rendre le composant utilisable dans le superviseur
33 Création de l'arborescence du module
34 =======================================
35 Dans un premier temps, on se contentera de mettre dans le module exemple un composant
36 SALOME écrit en C++ qui sera chargeable par un container C++.
37 Il suffit donc d'une interface idl et d'une implantation C++ du composant.
38 Pour mettre en oeuvre ceci dans un module SALOME, il nous faut reproduire l'arborescence de
39 fichier standard suivante::
65 Pour cela, on recopie l'arborescence de PYHELLO, et on modifie où nécessaire
68 cp -r PYHELLO1_SRC HELLO1_SRC
70 mv idl/PYHELLO_Gen.idl idl/HELLO_Gen.idl
71 mv src/PYHELLO src/HELLO
76 Dans le répertoire idl, nous modifions le fichier idl HELLO_Gen.idl : le
77 module défini est renommé HELLO_ORB, et l'interface en HELLO_Gen.
78 Le service rendu reste le même : à partir d'une chaine de caractères
79 fournie comme unique argument, retour d'une chaine de caractères obtenue
80 par concaténation de "Hello, " et de la chaine d'entrée.
81 Ce service est spécifié par la fonction makeBanner.
83 Un utilitaire de documentation basé sur doxygen a été mis en place pour
84 compiler une documentation des services corba à partir de commentaires se
85 trouvant dans les fichiers idl. Nous rajouter donc à notre idl quelques
86 commentaires, en respectant le formalisme doxygen.
87 Un commentaire doxygen commence par "/\*!" et se finit par "\*/".
88 Pour structurer un minimum les pages générées, on les regroupe par module ou
89 sujet. Dans notre exemple, nous utilisons la directive::
93 spécifiant que la documentation générée fait partie du groupe EXAMPLES.
94 (pour plus d'information sur doxygen, consulter le site http://www.doxygen.org).
96 Pour finir, nous mettons à jour le Makefile avec le nouveau nom de composant::
98 IDL_FILES = HELLO_Gen.idl
107 L'implémentation C++ de notre module CORBA HELLO (interface idl HELLO_Gen) est faite dans le répertoire
113 Au début du header de notre module (HELLO.hxx), les inclusions suivantes sont
116 #include <SALOMEconfig.h>
117 #include CORBA_SERVER_HEADER(HELLO_Gen)
118 #include "SALOME_Component_i.hxx"
120 Le fichier SALOMEconfig.h contient un certain nombre de définitions utiles
121 pour assurer l'indépendance du code par rapport à la version de CORBA
122 utilisée. SALOME_Component_i.hxx contient l'interface de la classe
123 d'implémentation C++ du composant de base Salome (idl Engines::Component).
124 Enfin, la macro CORBA_SERVER_HEADER assure l'indépendance des noms de fichiers
125 d'inclusion par rapport à l'implémentation de l'ORB CORBA.
127 Après cela, nous définissons une classe d'implémentation, nommée HELLO, dérivant de
128 POA_HELLO_ORB::HELLO_Gen (classe abstraite générée automatiquement par CORBA lors de la
129 compilation de l'idl) et de Engines_Component_i (car l'interface idl HELLO_Gen
130 dérive de Engines::Component comme tout composant Salome). Cette classe
131 contient un constructeur dont les arguments sont imposés par CORBA, un
132 destructeur virtuel, et une méthode makeBanner fournissant le service souhaité::
135 public POA_HELLO_ORB::HELLO_Gen,
136 public Engines_Component_i
139 HELLO(CORBA::ORB_ptr orb,
140 PortableServer::POA_ptr poa,
141 PortableServer::ObjectId * contId,
142 const char *instanceName,
143 const char *interfaceName);
145 char* makeBanner(const char* name);
148 La fonction makeBanner prend comme argument et renvoit un char*, projection C++ du type CORBA/IDL
150 La documentation complète du mapping c++ de l'IDL est fournie par l'OMG sur
151 son site internet : http://www.omg.org/cgi-bin/doc?ptc/00-01-02.
153 Enfin, nous fournissons l'interface (normalisé) de la fonction HELLOEngine_factory, qui
154 sera appelée par le "FactoryServer C++" pour charger le composant HELLO:
158 PortableServer::ObjectId * HELLOEngine_factory(
160 PortableServer::POA_ptr poa,
161 PortableServer::ObjectId * contId,
162 const char *instanceName,
163 const char *interfaceName);
166 Dans le fichier source (HELLO.cxx) se trouvent les définitions
167 du constructeur et de la fonction d'instanciation
168 HELLOEngine_factory (toutes deux normalisées!), et de makeBanner:
172 char* HELLO::makeBanner(const char* name)
174 string banner="Hello, ";
176 return CORBA::string_dup(banner.c_str());
179 Dans cette fonction, l'emploi de string_dup (fonction déclarée dans le
180 namespace CORBA) n'est pas obligatoire (on aurait pu utiliser l'opérateur new),
181 mais conseillé car ces fonctions permettent aux ORB d'utiliser des mécanismes
182 spéciaux de gestion de la mémoire sans avoir à redéfinir les opérateurs new
188 Dans le makefile, il faut définir certaines cibles::
190 VPATH=.:@srcdir@:@top_srcdir@/idl
191 LIB = libHELLOEngine.la
193 LIB_SERVER_IDL = HELLO_Gen.idl
194 LIB_CLIENT_IDL = SALOME_Component.idl SALOME_Exception.idl Logger.idl
195 CPPFLAGS += -I${KERNEL_ROOT_DIR}/include/salome
196 LDFLAGS+= -lSalomeContainer -lOpUtil -L${KERNEL_ROOT_DIR}/lib/salome
198 Passons en revue chacune de ces cibles.
200 - LIB contient le nom *normalisé* (lib<Nom_Module>Engine.la) le nom de la
201 librairie, LIB_SRC définit le nom des fichiers sources, et VPATH les
202 repertoire où l'on peut les trouver.
203 - LIB_SERVER_IDL contient le nom des fichiers idl implémentés par le module.
204 - LIB_CLIENT_IDL contient le nom des idl où sont définis les services CORBA
205 utilisés par le module. HELLO utilise Logger.idl via les macros "MESSAGE",
206 SALOME_Component.idl et SALOME_Exception.idl via l'héritage de HELLO_ORB
207 - Il faut ajouter à CPPFLAGS le chemin pour les fichiers includes utilisés
208 (SALOMEconfig.h, SALOME_Component_i.hxx et utilities.h se trouvent dans
209 ${KERNEL_ROOT_DIR}/include/salome)
210 - La classe HELLO utilise les librairies lib (pour Engines_Component_i) et
211 libOptUtil (pour PortableServer et Salome_Exception). On indique donc le nom
212 de ces librairies et leur chemin dans LDFLAGS.
213 D'autres librairies sont souvent utiles, par exemple libsalomeDS si on
214 implémente la persistence, ou libSalomeNS si on utilise le naming service.
217 Pilotage du composant depuis Python (mode TUI)
218 ==============================================
220 Lors de la compilation du module, la cible lib du Makefile dans /idl a
221 provoqué la génération d'un stub python (souche côté client générée à partir
222 de l'idl et offrant une interface dans le langage client - ici python.
223 Concrètement, un module python HELLO_ORB contenant une classe
224 _objref_HELLO_Gen sont créés, permettant de faire appel aux services de notre
225 module C++ depuis python. Mettons ceci en application. Pour cela, nous lançons
228 cd $HELLO_ROOT_DIR/bin/salome
229 python -i runSalome.py --modules=HELLO --xterm --logger --containers=cpp,python --killall
231 Depuis la fenêtre python, nous importons le module LifeCycle, et utilisons ses
232 services pour charger notre composant Dans la conteneur C++ FactoryServer::
234 >>> import LifeCycleCORBA
235 >>> lcc = LifeCycleCORBA.LifeCycleCORBA(clt.orb)
237 >>> hello = lcc.FindOrLoadComponent("FactoryServer", "HELLO")
239 L'import de HELLO_ORB est nécessaire avant l'appel de FindOrLoadComponent,
240 pour permettre de retourner un objet typé (opération de "narrowing"). Sinon,
241 l'objet retourné est générique de type Engines::Component. Vérifions que notre
242 objet hello est correctement typé, et appelons le service makeBanner::
245 <HELLO_ORB._objref_HELLO_Gen instance at 0x8274e94>
246 >>> mybanner=hello.makeBanner("Nicolas")
250 Les commandes précédentes ont été regroupées dans la fonction test du script
260 Pour aller plus loin dans l'intégration de notre module, nous allons ajouter
261 une interface graphique (développée en Qt), s'intégrant dans l'interface
262 applicative de Salome (IAPP).
263 On ne détaillera pas ici le fonctionnement de l'IAPP de Salome, mais pour
264 résumer, l'IAPP gère une boucle d'évènements (clics souris, clavier, etc), et
265 redirige après traitement ces évènements vers le module actif (le principe est
266 qu'à un instant donné, *un* module est actif. Lorsqu'un module est activé, son
267 IHM est chargée dynamiquement).
268 Le programmeur de la GUI d'un module a donc à charge de définir les méthodes
269 permettant de traiter correctement les évènements transmis. Parmi ces
270 méthodes, citons les principales : OnGUIEvent(), OnMousePress(), OnMouseMove(),
271 OnKeyPress(), DefinePopup(), CustomPopup().
278 La description des items de notre module se fait dans le fichier XML
279 /ressources/HELLO_en.xml. Ce fichier est utilisé par l'IAPP pour charger
280 dynamiquement l'IHM du module quand celle-ci est activée.
281 Le principe est de définir par des balises les menus et boutons souhaités, et
282 d'y associer des ID, qui seront récupérés par les fonctions gérant les
283 évènemements IHM. Plusieures possibilités sont offertes:
285 - ajout d'items à des menus déjà existant, auquel cas il faut reprendre les
286 balises du menu pré-existant, et y ajouter les nouveaux items. Dans
287 l'exemple qui suis, on ajoute le Menu **Hello** et l'item **MyNewItem** au
288 menu File, dont l'ID vaut 1::
290 <menu-item label-id="File" item-id="1" pos-id="">
291 <submenu label-id="Hello" item-id="19" pos-id="8">
292 <popup-item item-id="190" pos-id="" label-id="MyNewItem" icon-id="" tooltip-id="" accel-id="" toggle-id="" execute-action=""/>
297 - Création de nouveaux menus. Pour le module HELLO, nous ajoutons un menu
298 HELLO, avec un unique item de label "Get banner"::
301 <menu-item label-id="HELLO" item-id="90" pos-id="3">
302 <popup-item item-id="901" label-id="Get banner" icon-id="" tooltip-id="Get HELLO banner" accel-id="" toggle-id="" execute-action=""/>
306 - Ajout d'un bouton dans la barre à boutons. Dans l'exemple suivant, nous
307 créons un deuxième point d'entrée pour notre action "Get banner", sous forme
308 d'un bouton associé au même ID "901". L'icône est spécifiée par la le champ
309 icon-id, qui doit être un fichier graphique 20x20 pixels au format png::
311 <toolbar label-id="HELLO">
312 <toolbutton-item item-id="901" label-id="Get banner" icon-id="ExecHELLO.png"
313 tooltip-id="Get HELLO banner" accel-id="" toggle-id="" execute-action=""/>
318 A chaque menu ou item est associé un ID. Les numéros entre 1 et 40 sont
319 réservés à l'IAPP. Les numéros d'ID suivent une certaine règle, quoique
320 celle-ci ne soit pas obligatoire. Au menu "HELLO" est associé l'ID 90. Son
321 unique item "Get banner" a l'ID 901. Un deuxième item aurait l'ID 902, et un
325 Implémentation de l'IHM
326 -----------------------
328 L'implémentation C++ de l'IHM est faite dans le répertoire /src/HELLOGUI.
329 Le header HELLOGUI.h déclare de la classe HELLOGUI, et
330 contient des directives Qt (Q_OBJECT). De ce fait, il doit être processé par
331 le compilateur moc (Qt Meta Model Compiler). Pour cette raison, l'extension du
332 fichier est .h et dans le Makefile nous ajoutons la cible::
336 Le fichier source HELLO.cxx contient la définition des fonctions membres, et
337 le Makefile permet de construire une librairie libHELLOGUI (le nom est
338 normalisé poour permettre le chargement dynamique : lib<NomModule>GUI.
340 Gestion des évènements
341 ``````````````````````
342 Pour l'IHM d'HELLO, nous définissons la fonction HELLOGUI::OnGUIEvent, qui
343 sera appelé à chaque évènement. Cette fonction contient essentiellement une
344 structure switch permettant de traiter l'ID reçu en argument::
346 switch (theCommandID)
349 // Traitement de "Get banner"
352 // Traitement de "MyNewItem"
356 Le traitement standard consiste à récupérer des données d'entrée (ici, le
357 prénom via une fenêtre de dialogue QInputDialog::getText), à récupérer une
358 poignée sur le composant CORBA interfacé afin d'appeler le service souhaité
359 (ici, getBanner), et d'afficher le résultat obtenu ().
363 Pour les dialogues avec l'utilisateur, il est possible d'utiliser n'importe
364 quelle classe fournie par Qt (http://doc.trolltech.com/3.2/classes.html).
365 Cependant, lorque c'eset possible, il est préférable d'utiliser les fonctions
366 QAD (Qt Application Desktop), définies dans KERNEL_SRC/src/SALOMEGUI, qui
367 encapsulent les fonctions Qt correspondantes et gèrent mieux les
368 communications avec l'IAPP. Ainsi, dans HELLOGUI, nous utilisons la classe
369 QAD_MessageBox en lieu et place de la classe Qt QMessageBox.
372 Gestion du multi-linguisme
373 ``````````````````````````
374 Qt fournit un outil d'aide au support du multi-linguisme. Celui-ci est
375 repris dans salome. Le principe est simple : toutes les chaînes de caractères
376 utilisées pour les labels des menus et les dialogues avec l'utilisateur
377 sont encapsulés dans des appels à la fonction Qt tr() (pour translate), qui
378 prend en argument un nom de label. Par exemple, pour demander à l'utilisateur
379 de rentrer un prénom, nous utilisons la fonction getText, où les deux premiers
380 arguments sont des labels encapsulés par tr()::
382 myName = QInputDialog::getText( tr("QUE_HELLO_LABEL"), tr("QUE_HELLO_NAME"),
383 QLineEdit::Normal, QString::null, &ok);
385 Le nom des label est préfixé à titre indicatif par trois lettres et un underscore. Les codes
386 suivants sont utilisés::
389 - BUT_ : label bouton
390 - TOT_ : aide tooltip
391 - ERR_ : message d'erreur
392 - WRN_ : message d'alerte
393 - INF_ : message d'information
395 - PRP_ : prompt dans la barre des status
398 La traduction des labels encapsulés par tr() est faite pour différents
399 langages cibles (par exemple français et anglais) dans des fichiers nommés "<nom_module>_msg_<langage>.po".
400 <langage> correspond au code du langage, on a choisi **en** pour l'anglais et
401 **fr** pour le français. Ce fichier doit contenir pour chaque clé sa
402 traduction, par exemple::
404 msgid "HELLOGUI::INF_HELLO_BANNER"
405 msgstr "HELLO Information"
407 Le squelette de ce fichier peut être généré par l'utilitaire Qt findtr::
409 findtr HELLOGUI.cxx > HELLO_msg_en.po
411 puis éditer le fichier HELLO_msg_en.po pour remplir les traductions.
412 Ces fichiers sont ensuite compilés par l'utilitaire **msg2qm** pour générer
413 des binaires *.qm*. Pour cela, il faut remplir la cible LIB_MOC dans le
416 PO_FILES = HELLO_msg_en.po HELLO_msg_fr.po
418 Pour l'utilisateur final, le choix du langage se fait au niveau de chaque
419 module dans le fichier ressources/config, en utilisant la commande::
425 Règles syntaxiques de nommage
426 =============================
428 Dans ce qui précède, nous avons utilisé un certain nombre de règles de
429 nommage. Le présent chapitre se propose de faire le point sur ces règles.
430 Celles-ci ne sont pas toutes obligatoires, mais simplifient la compréhension
433 +-------------------+------------------+----------------+---------------------------------------+
434 | Règle | Formalisme | Exemple HELLO | Commentaire |
435 +===================+==================+================+=======================================+
436 | Nom du module | <Module> | HELLO | C'est le nom qui figure dans le |
437 | | | | catalogue des modules |
438 +-------------------+------------------+----------------+---------------------------------------+
439 | Base CVS | <Module> | EXAMPLES | Si la base cvs contient plusieurs |
440 | | | | modules, on prend un autre nom |
441 +-------------------+------------------+----------------+---------------------------------------+
442 | Repertoire source | <Module>_SRC | HELLO1_SRC | L'indice 1 est utilisé car on prévoit |
443 | | | | plusieurs version du module |
444 +-------------------+------------------+----------------+---------------------------------------+
445 | Fichier idl | <Module>_Gen.idl | HELLO_Gen.idl | |
447 +-------------------+------------------+----------------+---------------------------------------+
448 | Nom du module | <Module>_ORB | HELLO_ORB | On évite d'utiliser le nom du module |
449 | CORBA | | | (conflits) |
450 +-------------------+------------------+----------------+---------------------------------------+
451 | Nom de | <Module>_Gen | HELLO_Gen | La compilation de l'idl génère une |
452 | l'interface CORBA | | | classe abstraite |
453 | | | | POA_<Module>_ORB::<Module>_Gen |
454 +-------------------+------------------+----------------+---------------------------------------+
455 | fichier source | <Module>.cxx | HELLO.cxx | Dans le répertoire /src/<Module> |
457 +-------------------+------------------+----------------+---------------------------------------+
458 | Classe | <Module> | HELLO | Cette classe hérite de |
459 | d'implémentation | | | POA_HELLO_ORB::HELLO_Gen |
460 +-------------------+------------------+----------------+---------------------------------------+
461 | Fonction | <Module>_ | HELLO_Engine | Cette fonction est appelée par |
462 | d'instanciation | Engine_factory | factory | le FactoryServer de Salome |
463 +-------------------+------------------+----------------+---------------------------------------+
464 | Catalogue des | <Module>Catalog | HELLOCatalog | Dans /ressources |
465 | modules | .xml | .xml | |
466 +-------------------+------------------+----------------+---------------------------------------+
467 | Nom de la | lib<Module>Engine| libHELLOEngine | Dans le répertoire /src/<Module> |
468 | librairie C++ | | | |
469 +-------------------+------------------+----------------+---------------------------------------+
470 | Librairie C++ | lib<Module>GUI | libHELLOGUI | Dans le répertoire /src/<Module>GUI |
472 +-------------------+------------------+----------------+---------------------------------------+
473 | Variable | <Module>_ROOT_DIR| HELLO_ROOT_DIR | |
474 | d'environnement | | | |
475 +-------------------+------------------+----------------+---------------------------------------+
476 | ... | ... | ... | ... |
478 +-------------------+------------------+----------------+---------------------------------------+