4 hxx2salome : a SALOME component generator
5 ==========================================
9 This document is the following of the HELLO component documentation [R1]_, which presented the basis for the implementation of a SALOME Component, and the CALCULATOR component documentation [H1]_, which introduced the use of MED within the SALOME context. These two examples showed that implementing a SALOME component doesn't require much imagination, it can be done by following some predefined rules. Thus, it is possible, provided a C++ standalone component, to wrap it into a SALOME Component automatically. This document presents hxx2salome, a prototype tool for automatic SALOME Component generation. This tool starts from the interface of a C++ component (an .hxx file), parse the public API , and use the type information to generate the SALOME component (the IDL interface, and its implementation).
14 In this chapter, we will create from scratch a new SALOME component that add and multiply integers, and compute a factorial. We suppose that hxx2salome (and the related tools SA_new_component and SA_build ) have been installed in a directory called $HXX2SALOME_ROOT_DIR , which is present in your $PATH .
16 C++ component implementation
17 ''''''''''''''''''''''''''''
19 The first thing to do is to implement the C++ engine that will perform the services. To do it, we use the SA_new_cpp_component tool:
26 $ SA_new_cpp_component CALC
28 adm archive AUTHORS build_configure ChangeLog configure.in.base
29 Makefile.am NEWS README rfind root_clean src
35 The SA_new_cpp_component CALC command has created a complete tree in directory CALC_CPP_SRC that allow you to build a C++ component with a SWIG python interface. Now we have to define the interface and implement it. For that, we edit and adapt the templates that have been generated in the source directory:
42 $ cd CALC_CPP_SRC/src/CALC/CALC_CXX # go in source dir
43 $ vi CALC.hxx # edit interface template and adapt it
51 unsigned fact(unsigned n);
52 int add(int n1, int n2);
53 int mul(int n1, int n2);
58 $ vi CALC.cxx # edit and adapt class implementation template
62 int CALCUL::add(int i1, int i2)
67 int CALCUL::mul(int i1, int i2)
72 unsigned CALCUL::fact(unsigned n)
75 for (unsigned i=n; i!=1; --i)
84 A template python test file was also generated, that can be callable both from python and from SALOME. You can if you wish edit it and add the tests you want:
95 if getenv("SALOMEPATH"):
98 my_CALC = salome.lcc.FindOrLoadComponent("FactoryServer", "CALC")
102 my_CALC=CALCSWIG.CALC()
106 print "Test Program of CALC component"
107 print "5 + 3 = ", my_CALC.add(5,3)
108 print "5 * 3 = ", my_CALC.mul(5,3)
109 print "5! = ", my_CALC.fact(5)
115 Only the last three lines were added, the beginning is part of generated template.
117 Compilation – Testing with python
118 '''''''''''''''''''''''''''''''''
120 It's time now to compile the component and test it. The component is built under Automake. The build procedure (build_configure, configure, make, make install) is explained in the README file. You can also use the SA_build tool:
127 $ cd ../../../.. # go in father directory of CALC_CPP_SRC
128 $ SA_build CALC_CPP_SRC
131 CALC_CPP_BUILD CALC_CPP_INSTALL CALC_CPP_SRC
133 $ ls CALC_CPP_INSTALL # check installation was done
136 # update pathes for component's use within python
137 $ setenv PYTHONPATH \
138 instalDir/bin/salome:instalDir/lib/salome:$PYTHONPATH
139 $ setenv LD_LIBRARY_PATH \
140 instalDir/lib/salome:$LD_LIBRARY_PATH
142 $ python # test component with python
144 Test Program of CALC component
152 Before testing the component from python, it was necessary to update LD_LIBRARY_PATH and PYTHON_PATH environment variables.
154 SALOME component generation
155 '''''''''''''''''''''''''''
157 The C++ engine is finished, and tested - the final step is the integration inside SALOME. This is done using hxx2salome tool (the options used here are explained in Chapter 8.2 – the tool also has a graphical interface):
164 hxx2salome -c -e ${INTEGRATION_ROOT}/my_env_products.sh instalDir/CALC_CPP_INSTALL \
165 CALC.hxx libCALCCXX.so ${INTEGRATION_ROOT}
167 $ source ${INTEGRATION_ROOT}/my_env_products.sh
168 $ runSalome –-modules=CALC # launch salome with CALC compo
170 >>> import CALC_test # import test case from python console
175 The component can now be used inside SALOME, from the python embedded console, or from Supervision.
179 What is a C++ component?
180 ------------------------
182 We explain in this paragraph what is meant by “C++ component” in this document. This definition is definitely not unique!
184 Let's first try to define more generally what components are. They are used to deliver reusable, “off-the-shelf” software unit for incorporation into large applications (such as frameworks) : a component can be deployed independently and is subject to third-party composition. Its aim is to improve efficiency for end-users. It has specified interfaces and explicit context dependencies only. It encapsulates small-scale abstractions within a given domain.
186 A C++ component is a “high level” unit of reuse, based upon some source code libraries (developed in FORTRAN, C or C++). It takes the form of a C++ class. Its interface is the public API of this class, and is declared in an include file. It is designed to collaborate with other components . Therefore its API emphasizes the logical chains of computation a user can perform, and the data that may be exchanged with external world conform to standards (for example in the SALOME context: basic types, MED types and XDATA types).
188 For being more concrete, let's take the (simple) example of a steady-state neutronic component developed by CEA to work in collaboration with thermal-hydraulic and fuel-pin mechanics components. The interface of this component is:
200 void compute_power(int nitermax=200,
202 double espeigenvalue=1e-4);
203 const MEDMEM::MESH& DKCore_get_mesh();
204 const MEDMEM::FIELD<double>* get_power();
205 void feedback(const MEDMEM::FIELD<double>& Tcomb,
206 const MEDMEM::FIELD<double>& Dmod);
213 It emphasizes the following chain of computation:
223 .. image:: images/10000000000003210000012BF34ED8EC.png
238 It is designed to exchange MED objects: export of the mesh used for computation, and the neutronic power field, and import of fuel temperature and moderator density fields for thermal-hydraulic feedback.
240 This component was implemented with a preexisting function library, that was used in a neutronic software context. The internal format for meshes and fields was converted in MED format for exchange purpose.
244 SALOME component architecture (insights)
245 ----------------------------------------
247 The SALOME component architecture is based on CORBA , and has been optimized for big data transfers. The key points of the architecture are:
249 * Distributed components with the appearance of proximity. There is no difference between local and distant objects, the network exchanges are done by the CORBA bus, or MPI or any other protocol, everything being totally transparent for the end user, who just has to call methods on his “apparently local” objects.
253 * Heterogeneities are hidden by CORBA , which is multi-language and multi-platform!
257 * An open architecture, object oriented, facilitating evolutions and integration of new components.
261 * On top of the CORBA layer has been developed a specialized layer ( KERNEL ), which offers services like management of the components and their life cycle, persistence of objects, resource management, logs and notification, supervision, and GUI .
265 * A common normalized data format for meshes an fields ( MED ), which facilitates exchanges between components and integration in the platform.
270 The components are usually developed with C++ or python – but this is not an obligation. For components based upon a C-compatible library of functions (Fortran, C, C++, ...), the proposed architecture is the following :
274 .. image:: images/100000000000030C00000270AD87A128.png
277 The low level libraries are wrapped in a high level C++ component, that is exported to Python using SWIG, and for distribution and coupling to SALOME using hxx2salome. Same scripts can be use in Python or within SALOME.
283 SALOME Component generation
284 ---------------------------
286 In this chapter, we explain briefly the approach used to pass from a C++ component (as described in chapter 1) to a SALOME component.
288 A SALOME component is defined by its IDL interface (as explained in ...). It is then implemented using a target language, for example C++ (this step is called the IDL to C++ mapping).
290 Here we are doing the opposite! We have a C++ component with its interface (the definition of a class in a header), and we want to get a SALOME component (with its IDL interface, implemented using the C++ component). For doing this, we have to invert the IDL to C++ mapping. This is not feasible in a most general way (because of course user-defined C++ types don't have their counterpart in IDL). But if we restrict the C++ type to the mapping of the IDL types supported by SALOME, then we have a way to go back from C++ to IDL .
292 The operations performed for the SALOME component generation are summed up in the following figure:
296 .. image:: images/1000000000000321000002300A9186FC.png
302 After these operations, the generated files are inserted in a template SALOME module (basically a clone of the CALCULATOR component described in ...). We obtain that way a full module ready for compiling.
307 The IDL code generation is based upon the type analysis of the C++ public API . Of course, generation can be done only if there is a CORBA equivalent type. This is the case for all basic types. It is also the case for the MED types, because a CORBA level was developed ( MESH , SUPPORT and FIELD interfaces), and a Client level that allow to create local C++ MED objects from CORBA objects. This last point is important for code generation, because it simplify it greatly! (The only thing to do is to create a client object and pass it to the C++ API ). The last supported types are vectors, they are treated using the Sender/Receiver mechanism of SALOME, thus optimizing the data transfer.
309 Correspondance for parameters
310 '''''''''''''''''''''''''''''
312 The following table resume all the supported C++ types for parameters, and the associated IDL type:
314 =========================================== =============================
315 *C++ Argument type* *IDL associated type*
316 =========================================== =============================
322 unsigned in unsigned long
323 const char* in string
324 const std::string& in string
330 unsigned& out unsigned long
331 std::string& out string
332 const MEDMEM::MESH& in SALOME_MED::MESH
333 const MEDMEM::MESH* in SALOME_MED::MESH
334 const MEDMEM::SUPPORT& in SALOME_MED::SUPPORT
335 const MEDMEM::SUPPORT* in SALOME_MED::SUPPORT
336 const MEDMEM::FIELD<double>* in SALOME_MED::FIELDDOUBLE
337 const MEDMEM::FIELD<double>& in SALOME_MED::FIELDDOUBLE
338 const std::vector<double>& in SALOME::SenderDouble
339 std::vector<double>*& out SALOME::SenderDouble
340 const std::vector< std::vector<double> >& in SALOME::Matrix
341 MEDMEM::FIELD<double>*& out SALOME_MED::FIELDDOUBLE
342 const MEDMEM::FIELD<int>* in SALOME_MED::FIELDINT
343 const MEDMEM::FIELD<int>& in SALOME_MED::FIELDINT
344 const std::vector<int>& in SALOME::SenderInt
345 std::vector<int>*& out SALOME::SenderInt
346 MEDMEM::FIELD<int>*& out SALOME_MED::FIELDINT
347 =========================================== =============================
350 As we can see, **it is very important to take great care of the qualifiers used in the C++ interface** because they are interpreted. The determination of the ``in/out`` qualifier of ``IDL`` parameters is based upon the ``const`` and reference qualifier of C++ parameters. Basic types (passed by value in C++) are considered in parameters, references to basic types are considered out parameters. For user defined types, the ``const`` qualifier is interpreted as in parameter, and reference to pointer as out parameter.
352 For simplification purpose, SALOME doesn't allow the use of ``IDL inout`` parameters. For this reason, **non const pointers or references are not treated** .
354 Correspondance for returned type
355 ''''''''''''''''''''''''''''''''
357 The mapping between C++ returned types and their IDL counterpart is similar, except that we don't have to care about in/out qualifier! (The const and reference qualifier don't discriminate IDL type, but this information will nevertheless be useful when generating IDL implementation for memory management).
361 ==================================== =========================
362 *C++ returned type* *IDL associated type*
363 ==================================== =========================
370 unsigned unsigned long
374 const MEDMEM::MESH& SALOME_MED::MESH
375 MEDMEM::MESH& SALOME_MED::MESH
376 const MEDMEM::MESH* SALOME_MED::MESH
377 MEDMEM::MESH* SALOME_MED::MESH
378 MEDMEM::SUPPORT* SALOME_MED::SUPPORT
379 const MEDMEM::FIELD<double>* SALOME_MED::FIELDDOUBLE
380 const MEDMEM::FIELD<double>& SALOME_MED::FIELDDOUBLE
381 MEDMEM::FIELD<double>* SALOME_MED::FIELDDOUBLE
382 MEDMEM::FIELD<double>& SALOME_MED::FIELDDOUBLE
383 std::vector<double>* SALOME::SenderDouble
384 const MEDMEM::FIELD<int>* SALOME_MED::FIELDINT
385 const MEDMEM::FIELD<int>& SALOME_MED::FIELDINT
386 MEDMEM::FIELD<int>* SALOME_MED::FIELDINT
387 MEDMEM::FIELD<int>& SALOME_MED::FIELDINT
388 std::vector<int>* SALOME::SenderDouble
389 std::vector<std::vector<double> >* SALOME::Matrix
390 ==================================== =========================
396 To finish, let's have a look on the IDL generated module corresponding to our neutronic component example:
402 module CoreComponent_ORB
404 interface CoreComponent_Gen : Engines::EngineComponent,
405 SALOME::MultiCommClass
409 void compute_power(in long nitermax,
411 in double espeigenvalue);
412 SALOME_MED::MESH get_mesh();
413 SALOME_MED::FIELDDOUBLE get_power();
414 void set_feedback(in SALOME_MED::FIELDDOUBLE Tcomb,
415 in SALOME_MED::FIELDDOUBLE Dmod);
425 As explained in [R2]_ and [H2]_, the IDL implementation consists in writing a servant (an object that will perform the IDL contract). The source of this servant is composed in two files, named in SALOME by convention <module_name>.hxx and <module_name>.cxx . The generated code for these two files is also based upon the type analysis of the C++ public API : for each C++ type, we know the IDL type that was associated (cf. Chapter 4), and consequently the code to generate. This code follows always the same scheme. We first generate the header <module_name>.hxx , which contains the class declaration of the servant, and is imposed by the rules of the C++ mapping of CORBA. We then generate <module_name>.cxx, which contains the class definition. For each method, we proceed in three steps :
427 * Arguments processing : conversion of the types imposed by C++ mapping rules to the type of the C++ component. Of course, this conversion is always possible, because we have restricted the C++ component type to the one for which this operation is possible!
431 * Call of the C++ component : the call is performed with the converted types of step 1.
435 * Post treatment of the returned argument : This operation is the opposite of first step : the type of the parameters returned by the C++ component are converted to match the C++ mapping rules.
439 This being abstract, let's examine the generated code for two of the CoreComponent example :
445 const MEDMEM::FIELD<double>* get_power();
446 void feedback(const MEDMEM::FIELD<double>& Tcomb,
447 const MEDMEM::FIELD<double>& Dmod);
456 SALOME_MED::FIELDDOUBLE get_power();
457 void set_feedback(in SALOME_MED::FIELDDOUBLE Tcomb,
458 in SALOME_MED::FIELDDOUBLE Dmod);
468 SALOME_MED::FIELDDOUBLE_ptr get_power();
471 SALOME_MED::FIELDDOUBLE_ptr Tcomb,
472 SALOME_MED::FIELDDOUBLE_ptr Dmod);
481 SALOME_MED::FIELDDOUBLE_ptr DKCORE_i::get_power()
483 beginService("DKCORE_i::get_power");
484 BEGIN_OF("DKCORE_i::get_power");
485 // Call cpp component
486 const MEDMEM::FIELD<double>* _rtn_cpp=cppCompo_->get_power();
487 // Post-processing & return
488 MEDMEM::FIELDDOUBLE_i* _rtn_field_i=new MEDMEM::FIELDDOUBLE_i(
489 const_cast<MEDMEM::FIELD<double>*>(_rtn_cpp),false);
490 SALOME_MED::FIELDDOUBLE_ptr _rtn_ior = _rtn_field_i->_this();
491 endService("DKCORE_i::get_power");
492 END_OF("DKCORE_i::get_power");
496 void DKCORE_i::set_feedback(
497 SALOME_MED::FIELDDOUBLE_ptr Tcomb,
498 SALOME_MED::FIELDDOUBLE_ptr Dmod)
500 beginService("DKCORE_i::set_feedback");
501 BEGIN_OF("DKCORE_i::set_feedback");
502 // Arguments processing
503 MEDMEM::FIELDClient<double> _Tcomb(Tcomb);
504 MEDMEM::FIELDClient<double> _Dmod(Dmod);
505 // Call cpp component
506 cppCompo_->set_feedback( _Tcomb, _Dmod);
507 // Post-processing & return
508 endService("DKCORE_i::set_feedback");
509 END_OF("DKCORE_i::set_feedback");
515 The IDL generated part is driven by the tables given in Chapter 5.1 and 5.2. You can check for example that in SALOME_MED::FIELDDOUBLE correspond in table 5.1 to C++ type const MEDMEM::FIELD<double>&.
517 The CoreComponent_i.hxx interface is imposed by the CORBA norm (C++ mapping).
519 Finaly, the implementation of methods in CoreComponent_i.cxx is done in three steps. First step is argument processing : we convert the Corba types in order to call the C++ component. In set_feedback method, this consists in creating FIELDClient from the received FIELDDOUBLE_ptr . Second step is the call of C++ component method with converted types of step 1. Last step is to create Corba returned types from the types returned bu C++. In get_power method, we wrap the returned const MEDMEM::FIELD<double>* in a Corba field FIELDDOUBLE_ptr .
521 A last word about memory management. The Corba field created in get_power method doesn't take ownership of the C++ field it wrap (false parameter in the constructor). This is due to the fact that the C++ component method return a **const** field, thus indicating it owns the returned field. With a non const field, the Corba field would have get ownership (true parameter passed to the constructor), which means that deletion of Corba field causes deletion of C++ field).
533 The generator is a script file called hxx2salome , written in bash, which manages:
535 * the code generation,
539 * the compilation of generated module,
543 * the update of SALOME environment file.
547 This script can be used without any compilation. A Graphical User Interface, named ghx2salome was developed (with Qt) to wrap the script, which need to be installed and compiled:
554 cd <absolute path of HXX2SALOME>
556 ./configure --prefix=<absolute path of HXX2SALOME>
564 After installing, you have to set the environment variable HXXTOSALOME_ROOT_DIR to the bin directory that was installed and contains the hxx2salome, SA_new_cpp_component, SA_build scripts and the ghx2salome binary:
570 setenv HXX2SALOME_ROOT_DIR=<absolute path of HXX2SALOME>/bin
575 You may finally also configure the hxx2salome script (it is not mandatory, but may facilitate usage). For configuring the script, you can set the two following variables defined at the beginning :
577 * ENVIRON_FILE : SALOME environment file used for compilation. If present, hxx2salome will propose to compile new module (by sourcing ENVIRON_FILE file, and executing build_configure, configure, make & make install). It will also update this file with the new environment variable necessary to run generated module. This environment file can also be passed using **-e** option.
582 * CONFIGURE_OPTION : options passed to configure (for example --disable-debug or --enable-production ). This one cannot be passed by argument to the script. Default is no option.
591 The command to run the script is (supposing HXX2SALOME_ROOT_DIR is in your PATH ) :
597 hxx2salome [OPTIONS] CPPdir CPP.hxx libCPP.so SALOMEdir
602 where the mandatory components are:
604 * CPPdir : the installation directory (absolute path) of the c++ standalone component,
608 * CPP.hxx : the header name of the component,
612 * libCPP.so : the name of the shared library.
616 * SALOMEdir : the directory where you want to install generated SALOME component.
618 (Of course, CPP.hxx and libCPP.so have to be found in CPPdir )
622 In addition, you can use following options to transmit information to generator:
624 **-c** : to compile the component after code generation,
626 **-l** : to launch SALOME with the component after compilation,
628 **-e** : environment_file : to specify an SALOME environment file to source (for compiling)
632 The script gives user information on what have been done (checking of arguments, extraction of public function, which public function is compatible or not, the generated IDL, ...) to allow to check validity.
637 The GUI allow you to select the arguments with a file browser – thus avoiding spelling mistakes in file names that cause script abortion. The command to start the GUI is **gxx2salome** . It launch the following window :
641 .. image:: images/100000000000021500000199FE12879E.png
649 ---------------------
651 * The standalone C++ component should have a default constructor (a constructor without arguments). This is because there is no mechanism to transmit arguments from SALOME to a user-defined component. If your component needs information to be valid, you have to split construction and initialization, by adding a method that does initialization.
655 * Only methods with compatible types (types listed in Chapter 5, for which a conversion from CORBA to C++ is available) are treated. If a method contains non compatible types, it is just ignored (it is not blocking, you'll just get a SALOME component without the non compatibles methods).
659 * Avoid inline functions inside the header. They are not treated! If you have inlined functions in your header, you can remove them – or create a clone of your header only for generation purpose, without inline functions.
663 * The name of the C++ component (the name of the class), which provide the name of the SALOME component, should be uppercase. This strange limitation is due to SALOME.
667 * Typedef in the header are prohibited!
671 * The const qualifiers should be carefully analyzed, it impact the way SALOME will manage memory. The critical case is if you have an internal field, and you (wrongly) return a non const pointer on it. Because the pointer is non const, it is considered that the SALOME component takes ownership of the field, and consequently will delete it after usage – thus invalidating an internal pointer of your C++ component. Crash is the most frequent issue of this case...
675 * Avoid including headers in your component interface. Use as much as possible forward declaration. This common C++ rule will reduce the dependencies and fasten compilation. The side effect for the generator is that if you include headers in your component interface you'll have to indicate to the generator the paths where to find these included files.
679 * Avoid “using namespace” instruction in header (common C++ rule). MED and XDATA types should figure with the resolution operator. The generator doesn't recognize types if the namespace is not specified.
686 .. [H1] The MED Calculator component (N. Crouzet) (see :ref:`calculator`).
688 .. [H2] Integration of components into the SALOME environment (M. Tajchman) (see :ref:`components`)