This example is the following of the HELLO component. It’s purpose is to show how to quickly create a new SALOME module, to introduce the use of MEDCoupling objects within SALOME/Corba context, to demonstrate the mechanism of exceptions, and to introduce the use of SALOME supervisor.
Let’s go back a little bit on the different MED libraries (a web site giving general information and documentation about MED / MEDCoupling project is accessible from the SALOME web site, links section).
The first level (called “MED File”) is a C and Fortran API that implements mesh and field persistence. The definition of the mesh format (the definition of the implemented entities, the connectivity references ...) is done in [R3]. The use of the API is documented by [R4] and [R5]. The produced files are based on hdf format, and have the “.med” extension.
The second level library is called “MEDCoupling”, which is a C++ API that allows to create mesh and field objects in memory and manipulate them. Mesh creation can be done from scratch using set functions, or by loading a file with a driver (see html documentation and [R6]). Fields are also created using drivers (MEDLoader), or by initialization with a user-defined analytic function. Supported file format are .med, .sauv (persistent format of the CASTEM code) and vtk (only in write mode). MEDCoupling was developed to allow the exchange of mesh and field objects in memory between solvers. It can also be used as an internal data structure for new solvers. A last purpose was to gather in the same place existing algorithms and services around meshes and fields :
There are two libraries on the third level: a python API generated by SWIG, and a CORBA interface to exchange meshes, fields objects between different processes and SALOME containers without using files:
Finally, on the client side, we will demonstrate the use of the MEDClient classes. These classes are proxy classes designed to facilitate and optimize interaction of distant MEDCoupling CORBA objects with local solvers. The Corba API is completely hidden to clients.
The first step when developing a new SALOME module is to create a directories tree with makefiles that allow you to compile a SALOME component. This directories tree must follow SALOME rules, which are described in [R1] and [R2]. Create a complete tree from scratch is complicated, and error prone. The easiest way consist to find an existing module that “looks like the one you need”, and copy it. A shell script was written to facilitate this step : renameSalomeModule . This utility replace, after copying a module, any occurrence of the old name by the new one, thus avoiding to forget one of them. In the following example, we create a CALCULATOR_SRC module by copying HELLO_SRC module, then renameSalomeModule replace any occurrence of HELLO by CALCULATOR in CALCULATOR_SRC module:
cp -r HELLO_SRC CALCULATOR_SRC
renameSalomeModule HELLO CALCULATOR CALCULATOR_SRC
The remaining charge for the developer is to define the module interface (by writing a CORBA IDL file), and to implement it. But before, you may want to check that your duplicated module still compiles
mkdir CALCULATOR_BUILD
mkdir CALCULATOR_INSTALL
cd CALCULATOR_BUILD
cmake -DCMAKE_BUILD_TYPE=<Mode> -DCMAKE_INSTALL_PREFIX=CALCULATOR_INSTALL ../CALCULATOR_SRC
make && make install
Where <Mode> is build mode (Release or Debug)
The chosen methods demonstrate the use of MED fields ( FIELDDOUBLE interface) as in/out parameters and return value.
#ifndef __CALCULATOR_GEN__
#define __CALCULATOR_GEN__
#include "SALOME_Component.idl"
#include "SALOME_Exception.idl"
#include "MEDCouplingCorbaServant.idl"
module CALCULATOR_ORB
{
/*! \brief Interface of the %CALCULATOR component
*/
interface CALCULATOR_Gen : Engines::EngineComponent
{
/*!
Calculate the maximum relative difference of field with the previous one.
At first call, store passed field and return 1.
*/
double convergenceCriteria(
in SALOME_MED::MEDCouplingFieldDoubleCorbaInterface field);
/*!
Apply to each (scalar) field component the linear function x -> ax+b.
Release field1 after use.
*/
SALOME_MED::MEDCouplingFieldDoubleCorbaInterface applyLin(
in SALOME_MED::MEDCouplingFieldDoubleCorbaInterface field1,
in double a1,
in double a2);
/*!
Addition of fields.
Return exception if fields are not compatible.
Release field1 and field2 after use.
*/
SALOME_MED::MEDCouplingFieldDoubleCorbaInterface add(
in SALOME_MED::MEDCouplingFieldDoubleCorbaInterface field1,
in SALOME_MED::MEDCouplingFieldDoubleCorbaInterface field2)
raises (SALOME::SALOME_Exception);
/*!
return euclidian norm of field
Release field after use.
*/
double norm2(in SALOME_MED::MEDCouplingFieldDoubleCorbaInterface field);
/*!
return L2 norm of field
Release field after use.
*/
double normL2(in SALOME_MED::MEDCouplingFieldDoubleCorbaInterface field);
/*!
return L1 norm of field
Release field after use.
*/
double normL1(in SALOME_MED::MEDCouplingFieldDoubleCorbaInterface field);
/*!
return max norm of field
Release field after use.
*/
double normMax(in SALOME_MED::MEDCouplingFieldDoubleCorbaInterface field);
/*!
This utility method print in standard output the coordinates & field values
Release field after use.
*/
void printField(in SALOME_MED::MEDCouplingFieldDoubleCorbaInterface field);
/*!
This method clones field in four examples.
Release field after use.
*/
void cloneField(
in SALOME_MED::MEDCouplingFieldDoubleCorbaInterface field,
out SALOME_MED::MEDCouplingFieldDoubleCorbaInterface clone1,
out SALOME_MED::MEDCouplingFieldDoubleCorbaInterface clone2,
out SALOME_MED::MEDCouplingFieldDoubleCorbaInterface clone3,
out SALOME_MED::MEDCouplingFieldDoubleCorbaInterface clone4 );
};
};
#endif
The main points to note are:
After defining the interface of our component, we have to implement it by modifying the C++ implementation class ( CALCULATOR.hxx and CALCULATOR.cxx in src/CALCULATOR directory) and adapt it to the new IDL. In our case, this means to replace the HELLO method “ char* makeBanner(const char* name) ” with new methods that extends the IDL-generated implementation base class (as explained in the HELLO documentation, when compiling the IDL, CORBA generates an abstract base class, that the developer of the component has to derive and write code for the abstract methods). For the CALCULATOR component, the IDL-generated base class is called POA_CALCULATOR_ORB::CALCULATOR_Gen and is defined in generated header CALCULATOR_Gen.hh .
The IDL attributes are mapped to C++ methods. This operation is normalized by CORBA. Here, we give the mapping for the types involved in our example:
IDL Type | C++ type |
---|---|
double | CORBA::DOUBLE |
in MEDCouplingFieldDoubleCorbaInterface | MEDCouplingFieldDoubleCorbaInterface_ptr |
out MEDCouplingFieldDoubleCorbaInterface | MEDCouplingFieldDoubleCorbaInterface_out |
MEDCouplingFieldDoubleCorbaInterface | MEDCouplingFieldDoubleCorbaInterface_ptr |
MEDCouplingFieldDoubleCorbaInterface_ptr and MEDCouplingFieldDoubleCorbaInterface_out are C++ classes generated by the IDL compiler to map the MEDCoupling CORBA interface MEDCouplingFieldDoubleCorbaInterface . We will see below how to create such classes. But before, let’s have a look on the new header of the user-defined derived class CALCULATOR.hxx :
#ifndef _CALCULATOR_HXX_
#define _CALCULATOR_HXX_
#include <SALOMEconfig.h>
#include CORBA_SERVER_HEADER(CALCULATOR_Gen)
#include CORBA_CLIENT_HEADER(MEDCouplingCorbaServant)
#include "SALOME_Component_i.hxx"
class CALCULATOR:
public POA_CALCULATOR_ORB::CALCULATOR_Gen,
public Engines_Component_i
{
public:
CALCULATOR(CORBA::ORB_ptr orb,
PortableServer::POA_ptr poa,
PortableServer::ObjectId * contId,
const char *instanceName,
const char *interfaceName);
virtual ~CALCULATOR();
CORBA::Double convergenceCriteria(
SALOME_MED::MEDCouplingFieldDoubleCorbaInterface_ptr field);
CORBA::Double normMax(
SALOME_MED::MEDCouplingFieldDoubleCorbaInterface_ptr field1);
CORBA::Double normL2(
SALOME_MED::MEDCouplingFieldDoubleCorbaInterface_ptr field1);
CORBA::Double norm2(SALOME_MED::MEDCouplingFieldDoubleCorbaInterface_ptr field1);
CORBA::Double normL1(
SALOME_MED::MEDCouplingFieldDoubleCorbaInterface_ptr field1);
SALOME_MED::MEDCouplingFieldDoubleCorbaInterface_ptr applyLin(
SALOME_MED::MEDCouplingFieldDoubleCorbaInterface_ptr field1,
CORBA::Double a,CORBA::Double b);
SALOME_MED::MEDCouplingFieldDoubleCorbaInterface_ptr add(
SALOME_MED::MEDCouplingFieldDoubleCorbaInterface_ptr field1,
SALOME_MED::MEDCouplingFieldDoubleCorbaInterface_ptr field2)
throw ( SALOME::SALOME_Exception );
void printField(SALOME_MED::MEDCouplingFieldDoubleCorbaInterface_ptr field);
void cloneField(
SALOME_MED::MEDCouplingFieldDoubleCorbaInterface_ptr field,
SALOME_MED::FIELDDOUBLE_out clone1,
SALOME_MED::FIELDDOUBLE_out clone2,
SALOME_MED::FIELDDOUBLE_out clone3,
SALOME_MED::FIELDDOUBLE_out clone4);
};
extern "C"
PortableServer::ObjectId * CALCULATOREngine_factory(
CORBA::ORB_ptr orb,
PortableServer::POA_ptr poa,
PortableServer::ObjectId * contId,
const char *instanceName,
const char *interfaceName);
#endif
The main points to note are:
The implementation of the methods is very simple, thanks to the use of MEDClient library, which create an automatic link between CORBA and C++ objects. As a first example, let’s consider the implementation of the norm2 method. For being more concise, we do not explicit here the namespace SALOME_MED:: .
CORBA::Double CALCULATOR::norm2(SALOME_MED::MEDCouplingFieldDoubleCorbaInterface_ptr field1)
{
beginService( "CALCULATOR::norm2");
BEGIN_OF("CALCULATOR::Norm2(SALOME_MED::MEDCouplingFieldDoubleCorbaInterface_ptr field1)");
// Create a local field from corba field
// apply method normMax on it. When exiting the function
// f1 is deleted, and with it the remote corba field.
ParaMEDMEM::MEDCouplingAutoRefCountObjectPtr<ParaMEDMEM::MEDCouplingFieldDouble> f1=ParaMEDMEM::MEDCouplingFieldDoubleClient::New(field1);
CORBA::Double norme = f1->norm2();
END_OF("CALCULATOR::Norm2(SALOME_MED::MEDCouplingFieldDoubleCorbaInterface_ptr field1)");
endService( "CALCULATOR::norm2");
return norme;
}
The norm2 method receives as an input parameter a reference to a distant MEDCoupling CORBA field (named field1 ). It plays the role of the client toward the distant field field1 . As a client, we could directly call the methods of the MEDCouplingFieldDouble CORBA interface, for example call the getValue() method to retrieve the field values as an array. Doing this has some drawbacks. The transfer is not optimized because values are duplicated on server side. On the client side, we retrieve an array, but if we want to use existing solver or a function that takes an MEDCoupling C++ field, we need to rebuild a C++ field from the array, which is fastidious. That’s why we are using here MEDCouplingFieldDoubleClient class : MEDCouplingFieldDouble. This is a proxy C++ template class (also available for int type), that inherit the interface of the MEDCoupling C++ MEDCouplingFieldDouble class. Therefore, it can be used anywhere in place where a MEDCouplingFieldDouble is expected. The characteristics of this class are :
In our example, we simply create a MEDCouplingFieldDoubleClient , and then call on it the norm2 method of the MEDCoupling C++ API :
ParaMEDMEM::MEDCouplingAutoRefCountObjectPtr<ParaMEDMEM::MEDCouplingFieldDouble> f1=ParaMEDMEM::MEDCouplingFieldDoubleClient::New(field1);
CORBA::Double norme = f1->norm2();
A client class was also created for MESH, called MESHClient , with the same characteristics. For meshes, all the arrays (connectivities, coordinates) are transferred on demand, which is generally more interesting than for fields (where we usually need to retrieve values soon or later).
BEGIN_OF et END_OF macros are used to send traces to standard output when working on debug mode. BeginService and endService macros are used to send signals to the Supervisor to let him know the state of computation.
As a second example, let consider the applyLin method, which plays both the role of client and server:
SALOME_MED::MEDCouplingFieldDoubleCorbaInterface_ptr CALCULATOR::applyLin(
SALOME_MED::MEDCouplingFieldDoubleCorbaInterface_ptr field1,
CORBA::Double a,CORBA::Double b)
{
beginService( "CALCULATOR::applyLin");
BEGIN_OF("CALCULATOR::applyLin");
// create a local field on the heap,
// because it has to remain after exiting the function
ParaMEDMEM::MEDCouplingAutoRefCountObjectPtr<ParaMEDMEM::MEDCouplingFieldDouble> f1=ParaMEDMEM::MEDCouplingFieldDoubleClient::New(field1);
int nbOfCompo=f1->getArray()->getNumberOfComponents();
f1->getArray()->rearrange(1);
ParaMEDMEM::MEDCouplingFieldDoubleServant *NewField=NULL;
SALOME_MED::MEDCouplingFieldDoubleCorbaInterface_ptr myFieldIOR = NULL;
f1->applyLin(a,b);
f1->getArray()->rearrange(nbOfCompo);
// create servant from f1, give it the property of c++
// field (parameter true). This imply that when the
// client will release it's field, it will delete
// NewField,and f1.
NewField = new ParaMEDMEM::MEDCouplingFieldDoubleServant(f1);
// activate object
myFieldIOR = NewField->_this() ;
END_OF("CALCULATOR::applyLin");
endService( "CALCULATOR::applyLin");
return myFieldIOR;
The method is client for the parameter field field1 , and server for the returned field NewField . The client part (treatment of field1 ) is similar to the first example : we create with field1 a MEDCouplingFieldDoubleClient f1 and apply on it C++ method applyLin. The difference is that creation is done on the heap, not on the stack (we will explain why later) :
ParaMEDMEM::MEDCouplingFieldDoubleServant * NewField = new ParaMEDMEM::MEDCouplingFieldDoubleServant(f1);
f1->applyLin(a,b);
For the server part, we create a CORBA field (class ParaMEDMEM::MEDCouplingFieldDoubleCorbaInterface ), activate it and return a reference on it :
ParaMEDMEM::MEDCouplingFieldDoubleServant * NewField = new ParaMEDMEM::MEDCouplingFieldDoubleServant(f1);
SALOME_MED::MEDCouplingFieldDoubleCorbaInterface_ptr myFieldIOR = NewField->_this() ;
return myFieldIOR;
The parameters passed to the ParaMEDMEM::MEDCouplingFieldDoubleServant constructor are the C++ field f1 that is wrapped and used to give the services declared in IDL, and a boolean that indicates if ownership of wrapped field is transferred or not. If ownership is transferred, this means that when the CORBA field will be released by a client (for example by a MEDCouplingFieldDoubleClient created with a reference on it), it will delete the C++ field it holds. For example, the following code a hypothetic client could write would cause deletion of C++ field f1 :
SALOME_MED::MEDCouplingFieldDoubleCorbaInterface_ptr distant_f = CALCULATOR::applyLin(f,a,b);
ParaMEDMEM::MEDCouplingAutoRefCountObjectPtr<ParaMEDMEM::MEDCouplingFieldDouble> local_f=ParaMEDMEM::MEDCouplingFieldDoubleClient::New(distant_f);
// .. Use local_f
delete local_f; // causes release of distant_f and deletion
// of the C++ field it holds
This is why f1 is created on the heap and is not deleted : we want it to survive the end of the method! It will be deleted when client will release it reference.
[R1] | Guide for the development of a SALOME module in Python (C. Caremoli) (see Guide for the development of a SALOME module in Python). |
[R2] | Guide for the development of a SALOME module in C++ (N. Crouzet) (see Guide for the development of a SALOME module in C++). |
[R3] | Définition du modèle d’échange de données MED V3 (V. Lefebvre, E. Fayolle). |
[R4] | Guide de référence de la bibliothèque MED V3 (V. Lefebvre, E. Fayolle). |
[R5] | Guide d’utilisation de la bibliothèque MED V3 (V. Lefebvre, E. Fayolle). |
[R6] | User’s guide of MEDCoupling (Doc HTML MED). |