3 Introduction to the DSC programming model for SALOME components
4 ======================================================================
6 The purpose of this introduction is to provide essential concepts about how to use the DSC (Dynamic Software Component)
7 extension in SALOME components. It is intended for SALOME application developers who need to integrate calculation
8 codes in the form of SALOME components or supervisor services.
10 The principle of the programming model
11 ----------------------------------------
12 The DSC programming model is provided by the SALOME kernel and is available not only for services but also for
13 SALOME components without supervision. A SALOME application that would like to use the DSC extension always
14 follows this principle:
16 1. The different programs, codes and services that make up the application declare the various available ports
17 during the initialization phase.
18 2. The system (for example YACS or a user script) then connects the different ports to set up the different
19 communications between the codes.
20 3. The codes are run. When they want to use one of their DSC ports, the code asks to the system for a pointer
21 to its port. The system then checks that the port has been properly connected and/or properly declared.
22 4. The codes use the DSC ports.
24 Benefits of the DSC extension
25 ---------------------------------
26 The main benefits of the DSC extension to the SALOME programming model are as follows:
28 #. It enables use of datastream ports in SALOME
29 #. It enables use of the Calcium coupling tool in a calculation scheme without depending on the implementation of
30 Calcium outside SALOME. This results in better integration of this type of port, and greater flexibility in possible data types.
31 #. It offers the possibility of adding interface type ports into services.
34 +++++++++++++++++++++++++++++
35 DSC can add interface type ports to components and therefore to services. The datastream ports are nothing more
36 than a specialization of these interface ports. This interface is described through a CORBA interface.
37 Therefore it must be implemented by a CORBA object that may be either the component itself or an object that the
38 component uses. Properties may be attached to each interface port through a specific configuration object.
40 Calcium datastream ports
41 +++++++++++++++++++++++++++++
42 The SALOME kernel proposes a new implementation of Calcium ports. These ports are interface ports, therefore they can
43 be used like other interface ports by means through the C ++ API, or they can be used in the same way as with Calcium
44 through a C or FORTRAN interface. Finally, this new implementation was made with the objective of being able to port a
45 Calcium application into SALOME very quickly.
48 Connections between DSC ports
49 ------------------------------------
50 DSC ports may be input or output ports. Output ports are called <<uses>> because they enable the use of an interface
51 external to the component or the service. Input ports are called <<provides>> because they provide the implementation
52 of the port interface.
54 Service ports are described in an XML file that the catalog manager reads during creation of a calculation scheme.
55 On the other hand, ports are not described in the CORBA declaration of the service. This is why they are created and
56 added dynamically when the service is created and executed (in comparison with more static models like the CORBA Component Model).
57 DSC adds methods to components to declare interface ports.
61 void add_provides_port(...);
62 void add_uses_port(...);
63 Ports::Port get_provides_port(...);
64 uses_port get_uses_port(...);
66 Methods are provided to enable the connection of uses ports with provides ports of the same type (in other words
67 with the same CORBA interface). Furthermore, DSC notifies the user code about modifications to connections of
68 its ports through callback methods.
72 void connect_provides_port(...);
73 void connect_uses_port(...);
74 boolean is_connected(...);
75 void disconnect_provides_port(...);
76 void disconnect_uses_port(...);
77 void provides_port_changed(...);
78 void uses_port_changed(...);
80 Different combinations of connections between uses ports and provides ports are possible. A uses port may be connected
81 to several provides ports. Similarly, a provides port may be connected to several uses ports. Finally, uses ports know
82 the provides ports that they address, while a priori provides ports do not know the uses ports that called
83 them (obviously, application information can be added).
85 The two programming layers
86 ------------------------------------------------
87 The DSC component model is divided into two parts: DSC_Basic and DSC_User.
89 DSC_Basic is the lower layer of DSC. It provides the basic functions to add the concept of ports to a CORBA object.
90 It can be used to record servants of provides ports, declare uses ports and connect them. It is a so-called basic layer
91 because it does not provide much user convenience. In particular, a uses port is in the form of a sequence of references
92 to provides ports to which the uses port has been connected. The user is responsible for managing this sequence and
93 manually calling the different provides ports when uses ports are being used.
95 The DSC_User layer give a higher programming and encapsulation level of ports. Firstly, it enables the creation of
96 SALOME services with datastream (or interface) ports supplied by the SALOME platform. It provides an initialisation
97 method that the supervisor will call before the service is executed, which makes it possible to declare service ports
98 correctly. Finally, it offers abstraction for uses and provides ports. The two types of ports are implemented
99 by classes and the user thus has a uniform view of the two port types: 2 classes (and not one sequence and
100 one CORBA reference as in the DSC_Basic layer). Another benefit is the automatic management of the list of provides ports
101 connected to a uses port. The user code has a unique reference on its uses port; the uses port implementation is then
102 responsible for transmitting a call to the different port connections.
105 ------------------------------------------------
106 We will now discover the principles of DSC programming through different examples.
108 The first example sets up a component with a provides port. The second example sets up a second component
109 with a uses port that will be connected to the provides port in the Hello World example.
110 The third example shows how datastream ports are used in services.
111 The fourth example shows how the datastream ports factory system is used to add its own family in the programming model.
112 Finally, the fifth example shows how the new Calcium implementation is used in the SALOME context.
114 The following examples show examples of component programming. On the other hand, they do not contain the entire code, this
115 document only describes the new parts. The entire code for the examples is contained in the SALOME DSC_EXAMPLE_SRC module.
117 The first two examples are intended to help understand how the DSC ports are manipulated through the DSC_Basic layer.
118 On the other hand, the third example will have to be studied to see how services can be created with DSC ports through the DSC_User layer.
120 Access to the subversion base of the pal project must be authorised before the examples can be retrieved.
121 Then type the following line in a Unix shell::
123 svn co svn://<nom>@nepal/PAL/DSC_EXEMPLES_SRC/trunk DSC_EXEMPLES_SRC
128 The sources for this example are given in the src/Ex_Serveur directory.
130 The objective of this example is to create a component that provides a provides port. The following figure
131 illustrates the procedure. The component is called Ex_Serveur and it provides a provides port named Ex_Hello.
132 This provides port provides the HelloWorld interface.
134 .. image:: images/progdsc_img1.png
137 The first step is to define interfaces of the component and the port interface:
141 Interface HelloWorld : Ports::Port {
142 void say_hello(in string name);
145 Interface Ex_Serveur : Engines::DSC {};
147 The IDL file comprises firstly the declaration of the provides port interface that the component will provide.
148 In this case it is the HelloWorld interface. This interface is a classical CORBA interface. On the other hand, this
149 interface must inherit from Ports::Port if it is to be a DSC port. The Ex_Serveur component is also declared
150 as a CORBA interface that inherits from the Engines::DSC interface instead of Engines::EngineComponent.
151 Note that the provides port does not appear in the IDL definition of the component. The port is added and
152 declared in implementation sources of the component. It is added dynamically when the component is executed.
154 The objective now is to implement the component and the provides port. The provides port is implemented
155 through a C++ class that we will call HelloWorld_impl (see in the sources). This implementation is in no way
156 different from the implementation of a CORBA object. See the implementation of the say_hello method:
161 HelloWorld_i::say_hello(const char * name) {
162 std::cout << "Hello " << name << " ! " << std::endl;
166 The next step is to implement the component. We will be interested in the declaration of the component port
167 and the class that the component must inherit. The implementation of a component (Ex_Serveur_i class) that wants
168 to use DSC ports must inherit from the class named Engines_DSC_i. Obviously, it must also inherit from POA_Ex_Serveur.
169 See the declaration for Ex_Serveur_i class:
174 public Engines_DSC_i,
175 public POA_Ex_Serveur
179 Ex_Serveur_i(CORBA::ORB_ptr orb,
180 PortableServer::POA_ptr poa,
181 PortableServer::ObjectId * contId,
182 const char * instanceName,
183 const char * interfaceName);
185 virtual ~Ex_Serveur_i();
189 Two actions have to be performed before the provides port can be used:
192 2. Save the port in the component.
194 These two steps are implemented by adding a method called register_ports() to the Ex_Serveur_i class that is
195 called in the component factory before the factory returns the reference of the component to the container.
196 This method is implemented as follows:
201 Ex_Serveur_i::register_ports() {
203 // Create the provides port
204 _Ex_Hello_port = new HelloWorld_i();
205 _Ex_Hello_port_properties = new PortProperties_i();
207 // Save the provides port
208 add_provides_port(_Ex_Hello_port->_this(),
210 _Ex_Hello_port_properties->_this());
213 The method begins with creation of the provides port. The objective is to create the servant of the CORBA
214 interface of the port. An object also has to be created for port properties. The default object is used in
215 this example (supplied by the kernel). The port is then registered in the component through the
216 add_provides_port method provided by DSC.
218 The fact of inheriting from Engines_DSC_i obliges the component to implement two methods that are called
219 provides_port_changed() and uses_port_changed(). These two methods are callbacks that the system uses to notify
220 the component when the connections of its ports have changed. With the provides_port_changed() method, a warning
221 can be obtained when someone connects or disconnects on one of its provides ports.
222 In particular, the callback indicates how many clients use the provides port (connection_nbr argument).
223 This information is not used in this example. The uses_port_changed() method performs the same function as
224 the provides_port_changed() function, but for uses ports. The specific features will be described in the second example.
226 The documentation for the different Engines_DSC_i methods are provided in the Doxygen documentation of the SALOME kernel.
228 This example can be executed through the src/tests/test_Ex_Serveur.py test file, in the SALOME interpreter in terminal mode.
229 This script illustrates the use of DSC ports:
233 import LifeCycleCORBA
236 import HelloWorld_idl
238 lcc = LifeCycleCORBA.LifeCycleCORBA()
239 component = lcc.FindOrLoad_Component('FactoryServer', 'Ex_Serveur')
240 hello_port = component.get_provides_port("Ex_Hello", 0)
241 hello_port.say_hello("andre")
243 After the component has been created through LifeCycleCORBA , the script uses the get_provides_port method
244 to obtain a reference on the provides port of the component.
245 The reference obtained is then used to execute the say_hello method for the port.
248 +++++++++++++++++++++
249 The sources in this example are located in the src/Ex_Client directory and in src/Ex_Serveur.
251 The purpose of this example is to create a new component that will use the Ex_Hello port in the previous example, through a uses port.
253 The following figure represents the application:
255 .. image:: images/progdsc_img2.png
258 The Ex_Client component is described in the same way as the Ex_Serveur (Ex_Server) component in the IDL file.
259 The only difference is that a start() method is added in its interface. Since a component does not contain a
260 main function when it is created, a method to start execution of the component is required, which is why the
261 start method is defined.
263 The following is the IDL definition of the Ex_Client component:
267 Interface HelloWorld : Ports::Port {
268 void say_hello(in string name);
271 Interface Ex_Serveur : Engines::DSC {};
273 Interface Ex_Client : Engines::DSC {
277 The component now has to be implemented. As for a provides port, a uses port must be recorded in a component before
278 it can be used by the component. The uses port corresponds to a sequence of references to the provides ports to which
279 it has been connected: this is why it is not implemented by a class like a provides port. On the other hand, it is
280 always possible to add properties to the port.
281 The code for the register_ports() method for the Ex_Client component is given below:
286 Ex_Client_i::register_ports() {
288 // Create the properties object for the uses port.
289 _U_Ex_Hello_port_properties = new PortProperties_i();
291 // Add the uses port into the component
292 add_uses_port("IDL:HelloWorld:1.0",
294 _U_Ex_Hello_port_properties->_this());
298 A uses port is associated with a CORBA object type. Declaration of this type verifies if the uses port is
299 connected to a compatible provides port. In this example, the type of port (declared in the IDL) is HelloWorld.
300 CORBA proposes a character string corresponding to this type for each IDL type.
301 In this example, it is IDL:HelloWorld:1.0.
303 We now need to be able to use the uses port. To achieve this, the component asks the system to retrieve
304 the uses port using the get_uses_port() method. The port is in the form of a reference sequence on the different
305 provides ports. The references contained in this sequence are the provides ports to which the uses port has been
306 connected at the time that the get_uses_port() was called. The system uses the use_port_changed() method to
307 notify the user code every time that this reference list is changed to include an addition or a removal.
309 The Ex_Client component start() method will retrieve the U_Ex_Hello uses port and will call the say_hello() method
310 on the first reference. The code for this method is given below:
315 Ex_Client_i::start() {
317 // Retrieve the uses port U_Ex_Hello
318 Engines::DSC::uses_port * uport = get_uses_port("U_Ex_Hello");
320 // Retrieve the first reference in the sequence
321 _Ex_Hello_provides_port = HelloWorld::_narrow((* uport)[0]);
323 // Call the method on the port
324 _Ex_Hello_provides_port->say_hello(_instanceName.c_str());
327 Note that the _narrow method has to be used to transform the references contained in the uses port
328 into the provides port type, before the provides port can be used.
330 Datastream ports and services
331 ++++++++++++++++++++++++++++++
332 The sources in this example are located in the src/Ex_ProducteurConsommateur directory.
334 There are two purposes to this example. Firstly, the example shows how a service that wants to use DSC ports
335 is implemented. It then shows how datastream ports included in the SALOME kernel are used.
337 This example sets up two services that will be connected through a datastream port. The "produit" service of the
338 "Producteur" component will produce a dataflow, and the "consomme" service of the "Consommateur" component will display data.
340 The "produit" service terminates when it has sent all data that it has to produce. The number of data to be produced
341 is determined by the "nombre" dataflow port. The "consommateur" service needs to know how many data it has to retrieve before
342 it can terminate. As for the product service, this number is determined by the "nombre" dataflow port.
344 The following is the IDL definition of the two components:
348 interface Producteur : Engines::Superv_Component {
349 void produit(in long nombre);
352 interface Consommateur : Engines::Superv_Component {
353 void consomme(in long nombre);
356 In order to declare a component that will contain services using DSC ports, the component must inherit
357 from the Engines::Superv_Component interface and no longer from the Engines::EngineComponent interface.
358 In addition to adding the DSC interface to the component, Engines::Superv_Component adds the
359 init_service() method that the supervisor calls before the service is executed. The purpose of this
360 method is to enable the service designer to initialise ports for the service for connection before
361 the service is actually started. init_service() performs the same function as register_ports() in
362 the previous examples.
364 The next step is to implement these two components. The first difference from a classical component is that it
365 must inherit from the Superv_Component_i class. It must also implement the init_service() method.
367 The following is the implementation of the init_service method for the "Producteur" component:
372 Producteur_i::init_service(const char * service_name) {
373 CORBA::Boolean rtn = false;
374 string s_name(service_name);
375 if (s_name == "produit") {
376 add_port("BASIC_short", "uses", "produit_port");
382 The DSC_User layer that implements the Superv_Component_i class provides new methods for adding uses and provides ports.
383 They are methods of the add_port family (See the SALOME doxygen documentation). The purpose of these methods
384 is to enable creation and recording of a service port in a single step. They also enable the use of
385 datastream ports predefined in the SALOME kernel.
387 We chose to use the BASIC_short datastream port for the "produit" service. When the SALOME kernel provides
388 a datastream port, it always provides the implementation for the provides port and for the uses port.
389 The first part of the name (BASIC) identifies the datastream port family (for example like CALCIUM or PALM).
390 The second part of the name contains the transmitted data type, in this case a short. This port type forms
391 the first parameter of the add_port method. The other two arguments are the type of DSC port (uses or provides)
392 and the name of the port in the component.
394 When the objective is to use this port in the service, it will be necessary to retrieve a reference on the port, in the
395 same way as in the previous examples. New methods (like add_port) called get_port are available for this purpose.
396 The following is an example of code to use the get_port method:
400 uses_port * my_port = NULL;
401 get_port(my_port, "produit_port");
402 if (my_port != NULL) {
403 for (CORBA::Long i = 0; i < nombre; i++) {
404 data_short_port_uses * the_port = dynamic_cast<data_short_port_uses * >(my_port);
409 The get_port method has two arguments. The first will contain a pointer to the port and the second indicates
410 the requested port name. A generic pointer is obtained after calling the get_port method. The next step
411 is to change its type with the expected port type through a dynamic_cast. The port can then be used.
413 The DSC_User layer offers several signatures for the get_port and add_port methods, to make programming
414 more convenient. For example, the "Consommateur" component uses the template versions of these methods
415 to add and retrieve code.
419 data_short_port_provides * my_port = NULL;
420 my_port = get_port<data_short_port_provides>("consomme_port");
423 for (CORBA::Long i = 0; i < nombre; i++) {
424 cout << "Hello, I receive : " << my_port->get() << endl;
428 The list of the different port types supplied by the SALOME kernel is available in the
429 Doxygen documentation for the SALOME kernel.
431 Add datastream ports and/or interfaces
432 +++++++++++++++++++++++++++++++++++++++++++++++++
433 The sources for this example are located in the src/Ex_ProducteurConsommateur_Adv directory.
435 The purpose of this example is to show mechanisms used to add your specific types of ports into
436 the DSC_User layer. To do this, this example explains how to replace the BASIC_short port in the previous
437 example by its own port. Considering that the components in this example are practically identical, we will
438 only describe the declaration and implementation of the port in this document.
440 A Datastream port (or interface) family contains two different types of objects:
443 2. The implementation of ports
445 The DSC_User layer knows the types of Datastream ports through the factory pattern design.
446 For each family, a factory is recorded when the component is created. It is then used by
447 the component in the add_port(...) methods to create and save ports.
449 Firstly, its port(s) must be declared in an IDL file (MesPorts.idl in the example):
455 interface Mon_Type_De_Port : Ports::Data_Port {
456 boolean is_new_data_available();
458 interface Short_Mes_Ports : Mon_Type_De_Port {
459 void put(in short data);
464 In this example, a port is declared: Short_Mes_Ports. This is the port that is used to send a short, but
465 that can also be queried to find out if new data have arrived. On the other hand, the get() method has not
466 been declared in the IDL (although it is possible) because it is intended to be used locally.
468 The next step is to implement the port type. This is done by implementing the factory and the uses part
469 and the provides part of the port type.
471 A port factory is an object that implements the interface of the port_factory abstract class (see the Doxygen documentation).
472 The factory is called every time that a service adds a port in the component (uses or provides).
473 The factory is identified by a character string that identifies it to the component. The factories
474 must be recorded as early as possible. This is why factories are recorded in the component constructor.
476 The following figure shows how the factory is registered in the ProducteurAdv component:
480 ProducteurAdv_i::ProducteurAdv_i(CORBA::ORB_ptr orb,
481 PortableServer::POA_ptr poa,
482 PortableServer::ObjectId * contId,
483 const char * instanceName,
484 const char * interfaceName) :
485 Superv_Component_i(orb, poa, contId, instanceName, interfaceName)
488 _id = _poa->activate_object(_thisObj);
490 register_factory("MESPORTS", new mes_ports_factory());
493 In this example, the new port type is identified by the MESPORTS string. Note that the <<_>> symbol
494 may not be used in the name. It is used as a separator between the family name and the port type in the family (Eg: MESPORTS_short).
496 The next step is to implement the ports. The uses port part and the provides port part have to be implemented
497 for each port defined in the IDL. The implementation at the uses end must inherit from the uses_port class.
498 At the provides end, it must inherit from the provides_port class.
500 The uses_port and provides_port classes are abstract classes. They propose methods to automate registering
501 and management of ports. The component developer must implement these mechanisms in the DSC_Basic layer, while
502 the ports developer is responsible for these functions in the DSC_User layer.
504 The following methods are used:
510 virtual const char * get_repository_id() = 0;
512 virtual void uses_port_changed(Engines::DSC::uses_port * new_uses_port,
513 const Engines::DSC::Message message) = 0;
515 For the provides end:
517 virtual Ports::Port_ptr get_port_ref() = 0;
519 virtual void provides_port_changed(int connection_nbr,
520 const Engines::DSC::Message message) {};
522 The first step at the uses end is to use the get_repository_id() method. This obtains the CORBA typecode of the port.
523 The objective is to provide the same information as the first add_uses_port argument in the Basic layer.
524 The uses_port_changed(...) method enables the port to be notified about new connections with the provides port
525 and to manage the list of connections.
527 At the provides end, get_port_ref() obtains a CORBA reference on the servant. Finally, the provides_port_changed(...) method
528 can be overdefined if the provides port uses the information originating from connections / disconnections.
530 In the example, the provides port is implemented by the data_short_mes_ports_provides class and
531 the uses port is implemented by the data_short_mes_ports_uses class.