]> SALOME platform Git repositories - modules/yacs.git/blob - doc/cppsalome.rst
Salome HOME
mergefrom branch BR_V511_PR tag mergeto_trunk_03feb09
[modules/yacs.git] / doc / cppsalome.rst
1
2 :tocdepth: 3
3
4 .. _cppsalome:
5
6 ===========================================================
7 Guide pour le développement d'un module SALOME en C++
8 ===========================================================
9
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.
16
17
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.
24
25 Les différentes étapes du développement seront les suivantes :
26
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
32
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::
40
41   + HELLO1_SRC
42     + build_configure
43     + configure.in.base
44     + Makefile.in
45     + adm_local
46       + unix
47         + make_commence.in
48         + make_omniorb.in
49         + config_files
50     + bin
51       + VERSION
52       + runAppli.in
53       + runSalome.py
54     + idl
55       + Makefile.in
56       + HELLO_Gen.idl
57     + src
58       + Makefile.in
59       + HELLO
60         + Makefile.in
61         + HELLO.cxx 
62         + HELLO.hxx 
63     + doc
64
65 Pour cela, on recopie l'arborescence de PYHELLO, et on modifie où nécessaire
66 PYHELLO en HELLO::
67
68     cp -r PYHELLO1_SRC HELLO1_SRC
69     cd HELLO1_SRC
70     mv idl/PYHELLO_Gen.idl idl/HELLO_Gen.idl
71     mv src/PYHELLO src/HELLO
72
73
74 Interface idl
75 ==================
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.
82
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::
90
91     \ingroup EXAMPLES 
92
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).
95
96 Pour finir, nous mettons à jour le Makefile avec le nouveau nom de composant::
97     
98     IDL_FILES = HELLO_Gen.idl
99
100
101 Implémentation C++
102 ==================
103
104 Les sources
105 -----------
106
107 L'implémentation C++ de notre module CORBA HELLO (interface idl HELLO_Gen) est faite dans le répertoire
108 /src/HELLO::
109
110     HELLO.hxx
111     HELLO.cxx
112
113 Au début du header de notre module (HELLO.hxx), les inclusions suivantes sont
114 nécessaires::
115
116     #include <SALOMEconfig.h>
117     #include CORBA_SERVER_HEADER(HELLO_Gen)
118     #include "SALOME_Component_i.hxx"
119
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.
126
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é::
133
134     class HELLO:
135       public POA_HELLO_ORB::HELLO_Gen,
136       public Engines_Component_i
137     {
138     public:
139         HELLO(CORBA::ORB_ptr orb,
140                 PortableServer::POA_ptr poa,
141                 PortableServer::ObjectId * contId,
142                 const char *instanceName,
143                 const char *interfaceName);
144         virtual ~HELLO();
145         char* makeBanner(const char* name);
146     };
147
148 La fonction makeBanner prend comme argument et renvoit un char*, projection C++ du type CORBA/IDL
149 string. 
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.
152
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:
155 ::
156
157     extern "C"
158           PortableServer::ObjectId * HELLOEngine_factory(
159                 CORBA::ORB_ptr orb,
160                 PortableServer::POA_ptr poa,
161                 PortableServer::ObjectId * contId,
162                 const char *instanceName,
163                 const char *interfaceName);
164
165
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:
169
170 ::
171
172     char* HELLO::makeBanner(const char* name)
173     {
174         string banner="Hello, ";
175         banner+=name;
176         return CORBA::string_dup(banner.c_str());
177     }
178
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
183 globaux.
184
185 Makefile
186 --------
187
188 Dans le makefile, il faut définir certaines cibles::
189
190     VPATH=.:@srcdir@:@top_srcdir@/idl
191     LIB = libHELLOEngine.la
192     LIB_SRC = HELLO.cxx
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
197
198 Passons en revue chacune de ces cibles.
199
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.
215
216
217 Pilotage du composant depuis Python (mode TUI)
218 ==============================================
219
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
226 Salome en mode TUI::
227
228     cd $HELLO_ROOT_DIR/bin/salome
229     python -i runSalome.py --modules=HELLO --xterm --logger --containers=cpp,python --killall
230
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::
233
234     >>> import LifeCycleCORBA
235     >>> lcc = LifeCycleCORBA.LifeCycleCORBA(clt.orb)
236     >>> import HELLO_ORB
237     >>> hello = lcc.FindOrLoadComponent("FactoryServer", "HELLO")
238
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::
243
244     >>> print hello
245     <HELLO_ORB._objref_HELLO_Gen instance at 0x8274e94>
246     >>> mybanner=hello.makeBanner("Nicolas")
247     >>> print mybanner
248     Hello, Nicolas
249
250 Les commandes précédentes ont été regroupées dans la fonction test du script
251 /bin/runSalome.py.
252
253
254 Interface graphique
255 ===================
256
257 Introduction
258 ------------
259
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().
272
273 Choix des widgets
274 -----------------
275
276 Description xml
277 ```````````````
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:
284
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::
289
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=""/>
293           </submenu>
294           <endsubmenu />
295     </menu-item>
296
297 - Création de nouveaux menus. Pour le module HELLO, nous ajoutons un menu
298   HELLO, avec un unique item de label "Get banner"::
299
300     <menubar>
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=""/>
303      </menu-item>
304     </menubar>
305
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::
310
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=""/>
314     </toolbar>
315
316 Convention
317 ``````````
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
322 sous item l'ID 9021.
323
324
325 Implémentation de l'IHM
326 -----------------------
327
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::
333
334         LIB_MOC = HELLOGUI.h
335
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.
339
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::
345
346   switch (theCommandID)
347     {
348     case 901:
349       // Traitement de "Get banner"
350       ...
351     case 190:
352       // Traitement de "MyNewItem"
353       ...
354     }
355
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 ().
360
361 Classes disponibles
362 ````````````````````
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.
370
371
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()::
381
382         myName = QInputDialog::getText( tr("QUE_HELLO_LABEL"), tr("QUE_HELLO_NAME"),
383                                         QLineEdit::Normal, QString::null, &ok);
384
385 Le nom des label est préfixé à titre indicatif par trois lettres et un underscore. Les codes
386 suivants sont utilisés::
387
388  - MEN_ : label menu
389  - BUT_ : label bouton
390  - TOT_ : aide tooltip
391  - ERR_ : message d'erreur
392  - WRN_ : message d'alerte
393  - INF_ : message d'information
394  - QUE_ : question
395  - PRP_ : prompt dans la barre des status
396
397
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::
403
404     msgid "HELLOGUI::INF_HELLO_BANNER"
405     msgstr "HELLO Information"
406
407 Le squelette de ce fichier peut être généré par l'utilitaire Qt findtr::
408
409     findtr HELLOGUI.cxx > HELLO_msg_en.po
410
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
414 Makefile::
415
416     PO_FILES =  HELLO_msg_en.po HELLO_msg_fr.po
417
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::
420
421     langage=<langage>
422
423
424
425 Règles syntaxiques de nommage
426 =============================
427
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
431 si on les suit!
432
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  |                                       |
446 |                   |                  |                |                                       |
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>      |
456 |                   |                  |                |                                       |
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   |
471 | de l'IHM          |                  |                |                                       |
472 +-------------------+------------------+----------------+---------------------------------------+
473 | Variable          | <Module>_ROOT_DIR| HELLO_ROOT_DIR |                                       |
474 | d'environnement   |                  |                |                                       |
475 +-------------------+------------------+----------------+---------------------------------------+
476 | ...               | ...              | ...            | ...                                   |
477 |                   |                  |                |                                       |
478 +-------------------+------------------+----------------+---------------------------------------+
479