Why is CORBA used

Programming with CORBA and C ++

Martin Kompf

CORBA is a means for the relatively comfortable implementation of distributed software. This tutorial describes the introduction to the use of CORBA in C ++ programs. The free CORBA implementation MICO is used for the program examples (download).

content

Basics

CORBA (Common Object Request Broker Architecture) is a standard that describes the means of communication between different processes. In contrast to the conventional interprocess communication methods such as sockets or memory mapped files, CORBA relieves the software developer from having to worry about details such as communication protocols and data formats. Instead, CORBA offers the C ++ programmer's familiar view of objects that communicate with each other via method calls.

The CORBA implementation then takes care of the conversion and packaging of the data that has been passed to the method, of sending the method call over the network to the target object, of unpacking and reconverting the data there and of the actual method call on the target object. If the corresponding method should return data to the caller, then the whole thing is done again in the opposite direction. This process, which is hidden from the user, makes it possible, for example, to use an object in a Java program running under Linux that was implemented in C ++ and resides on a Windows NT computer.

CORBA is just a standard - not an implementation. However, there are various implementations of this architecture from a wide variety of manufacturers. Version 2.3.11 of MICO was used for the example programs contained in this tutorial. MICO is a fully featured and freely available CORBA implementation under the GNU copyleft.

The use of CORBA and MICO is to be demonstrated using a small distributed application for managing telephone numbers. This consists of a server that stores pairs of names and associated telephone numbers, and clients that are used to enter new names / numbers and for research. The development environment was MICO 2.3.11 under Linux with the gcc 3 and Windows 2000 with the Cygwin Toolkit 1.1. Before you can work with the examples, MICO must first be installed. Many Linux distributions such as Debian or Suse contain a MICO development environment as an installable package. Alternatively, you can download the source code, translate it and install it.

Interface definition

The first step in creating a CORBA application is the Definition of all required interfaces. Client and server later exchange their data via these interfaces. The interface definitions are made in a special language, the Interface Definition Language (IDL). Its syntax is very similar to a C ++ class definition. The intended example application manages with a single, simple interface called. This contains two methods: one for adding a new phone book entry and one for searching by name. The interface definition written in IDL is saved in a file IPBook.idl saved and looks like this:

#ifndef _IPBook_idl # define _IPBook_idl / ** * Interface for a very simple phone book ** / interface IPBook {/ * Add an entry * / void addEntry (instring name, instring number); / * Search for an entry * / string searchEntry (instring name); }; #endif

The most important difference to a C ++ class definition is that the direction of the data transfer must be specified for the method parameters. The specification used means that data is only transported from the calling party to the object, analogously then means a data transport back to the calling party and a bidirectional data transfer. A return value (like string in searchEntry) of course always means that data is transferred back to the caller and does not have to be specified here.

One more word about the general design of CORBA interfaces: Here you should always keep in mind that every method call is a network transmission! A considerable overhead arises, especially when only a few bytes of user data are transmitted. At this point, one must beware of theorists of object-oriented design, who package everything and anything in objects.

In our example application, from an object-oriented point of view, it would make perfect sense to have an additional interface Entry with methods like setName (), getName (), setNumber () and so on. The addEntry () function in IPBook would then receive an Entry object as a parameter. The result of this design would be three times the number of remote method calls, all of which go over the network. An absolute performance killer!

When designing CORBA interfaces, the following applies:

  • Minimize the number of interfaces!
  • Instead of n method calls with one parameter, a method call with n parameters is better!
  • Try to pack data that belong together in structures or fields rather than in objects (= interfaces)!

Implementation of the server object

After creating the interface definition, the next step is the Implementation of the server objects (servants). On the one hand, a server object has to implement the application logic - the functions addEntry () and searchEntry () have to be filled with life. On the other hand, the server object must also establish the connection to the CORBA implementation, more precisely to the Object Request Broker (ORB), which is responsible for mediating the distributed function calls. There are different objects for this connection adapter. The older one is the one Basic Object Adapter (BOA). The more extensive and flexible one has existed since the CORBA Standard 2.2 Portable Object Adapter POA. Although MICO provides both adapters in the current version, the following will only deal with the use of the modern POA.

When implementing the server objects, the IDL processor contained in MICO relieves the programmer of a lot of work. The IDL processor is called on the command line, first you should use

. /usr/local/lib/mico-setup.sh

set the correct environment (if MICO was installed in the / usr / local directory and bash is used - otherwise this line must be modified accordingly). The call

idl --poa --c ++ - suffix cpp IPBook.idl

generated from the interface definition file IPBook.idl the C ++ files IPBook.h and IPBook.cpp.

These files already contain a large part of the implementation of the server object. Among other things, these files provide a class that serves as the base class for the server object. Open the file with a text editor IPBook.h and if you look for the definition of the class in it, you will find the declaration of the purely virtual functions

virtualvoid addEntry (constchar * name, constchar * number) = 0; virtualchar * searchEntry (constchar * name) = 0;

These are the methods that were specified in the interface definition. The IDL data type has now only become the C ++ data type, the IDL attribute finds its equivalent in the specification of the function parameter.

The work still to be done by the programmer is now limited to writing a new class that is derived from and implements at least the two pure virtual functions addEntry () and searchEntry (). It has become common practice to name this class with the suffix, so in the example. The definition of the class is in the file IPBook_impl.h:

#ifndef IPBook_impl_h # define IPBook_impl_h 1 # include "IPBook.h" #include #include usingnamespace std; class IPBook_impl: virtualpublic POA_IPBook {public: // implement pure virtual functions from POA_IPBookvirtualvoid addEntry (constchar * name, constchar * number); virtualchar * searchEntry (constchar * name); private: map > _numbers; }; #endif

In addition to the necessary functions addEntry () and searchEntry (), the class also has the private member variable _numbers, which is used to record the data from the phone book. The definitions of your own constructors and the destructor are also common at this point. However, this relatively simple example manages with the standard constructor and destructor provided by the C ++ compiler.

The actual implementation of the class in the file IPBook_impl.cpp should now go smoothly:

#include #include "IPBook_impl.h" usingnamespace std; void IPBook_impl :: addEntry (constchar * name, constchar * number) {string nam = name; string num = number; _numbers [nam] = num; } char * IPBook_impl :: searchEntry (constchar * name) {map > :: iterator r; r = _numbers.find (name); if (r! = _numbers.end ()) {return CORBA :: string_dup ((* r) .second.c_str ()); } else {return CORBA :: string_dup ("???"); }}

It is important to consider (or to know!) Two things: The transfer parameters of the type occupy memory space on the heap. This is allocated by the caller of the function, i.e. the ORB. This memory area is released again by the ORB after the function has ended. Then no more references to this memory may exist in the server object! Therefore the parameters name and number are first in the C ++ strings nam and num copied, before they are inserted into the _numbers map (it is then copied again, but nam and num are on the stack and are destroyed when the function is exited).

Second, searchEntry () returns a pointer to a type of memory area to the ORB. So that no memory leaks can occur here, the CORBA standard stipulates that the ORB is responsible for releasing this memory area. The programmer must therefore ensure that he is returning memory that is on the heap. CORBA provides the function for this CORBA :: string_dup () to disposal.

The modules created so far can now be compiled:

mico-c ++ -c IPBook.cpp mico-c ++ -c IPBook_impl.cpp

creates the object modules IPBook.o and IPBook_impl.o. In order to bring these to life, the corresponding server and client applications are required.

The server application

All that is missing to create a complete CORBA server program is the appropriate one Main() Function. This is sensibly in its own source file, for example with the name cabsrv_co.cpp written. First, the header files are included - the ones supplied by MICO are absolutely necessary here CORBA.h as well as the declaration of the servant in IPBook_impl.h:

#include #include "IPBook_impl.h" #include usingnamespace std; int main (int argc, char ** argv) {int rc = 0;

Next, the ORB (Object Request Broker) and a POA Manager (POA: Portable Object Adapter) must be generated and initialized. For this simple example it is sufficient to use the root POA manager provided by the system. All CORBA system calls are encapsulated in a try-catch block:

try {// init ORB and POA Manager CORBA :: ORB_var orb = CORBA :: ORB_init (argc, argv); CORBA :: Object_var poaobj = orb -> resolve_initial_references ("RootPOA"); PortableServer :: POA_var poa = PortableServer :: POA :: _ narrow (poaobj); PortableServer :: POAManager_var mgr = poa -> the_POAManager ();

Now the servant can be created and activated. CORBA provides several activation methods, here the implicit activation used:

// create a new instance of the servant IPBook_impl * impl = new IPBook_impl; // activate the servant IPBook_var f = impl -> _this ();

Each CORBA server object naturally needs a unique identification so that a client can find the "right" servant. This identification is called IOR (Interoperable Object Reference). There are several options for transporting this IOR from the server to the client. The simplest (and only portable) method is to convert the IOR into a character string and save it in a file that the server and client have equal access to (alternatives to this will be discussed in detail in the next chapters):

// save the stringified object reference to file CORBA :: String_var s = orb -> object_to_string (f); ofstream out ("IPBook.ref"); out << s << endl; out.close ();

All the necessary preparations have now been completed, the POA manager can now be activated and the ORB can be started. This finally brings the servant to life:

// activate POA manager mgr -> activate (); // run the ORB orb -> run ();

The program does not normally return from this function call unless the ORB is explicitly shut down. In this case, the POA manager and servant should be cleaned up correctly:

poa -> destroy (TRUE, TRUE); delete impl; rc = 0; }

The rest of Main() The function now deals with the catching of exceptions from the CORBA system and the error output:

catch (CORBA :: SystemException_catch & ex) {ex -> _print (cerr); cerr << endl; rc = 1; } return rc; }

Our first CORBA server application is now fully programmed, it can now be programmed using

mico-c ++ -c cabsrv_co.cpp

to be translated. Finally, the object modules created so far must be linked together with the MICO library in order to obtain an executable program:

mico-ld -o cabsrv_co cabsrv_co.o IPBook.o IPBook_impl.o -lmico2.3.11

Now cabsrv_co can be started via the command line. If no error occurs, the program runs in the foreground with no further output. In order to be able to do anything meaningful at all, you still need programs that at least allow you to create new phone book entries and research them.

CORBA clients

The first thing to do is to create a program that allows entries to be made in the phone book. An entry on the command line in the form

cabadd_co name number

should make an entry with the name Surname and the number number add to the phone book. The program file cabadd_co.cpp created. Here I have to go again first CORBA.h be included. In contrast to the server program, however, the declaration of the servant must not be included here; the stub file generated by the IDL preprocessor is used instead IPBook.h used:

#include #include "IPBook.h" #include usingnamespace std;

In the main program, the ORB is initialized first and then a test for the presence of the command line arguments is carried out. This order makes sense, as it is possible to pass additional parameters for the ORB on the command line (we will get to know some of these parameters in the next chapter). The CORBA :: ORB_init (argc, argv) function evaluates these ORB-specific parameters and adjusts argc and argv accordingly. The subsequent test can then be limited to querying the program-specific command line arguments:

int main (int argc, char ** argv) {CORBA :: ORB_var orb = CORBA :: ORB_init (argc, argv); int rc = 0; if (argc! = 3) {cerr << "usage:" << argv [0] << "name number \ n"; exit (1); }

The client program also needs the information which CORBA server it should use. After all, several hundred servants can do their job in an extensive CORBA application! We remember: When programming the server in the previous chapter, the IOR of the servant is converted into a character string and written to the file IPBook.ref. This file is now read in by the client program:

ifstream in ("IPBook.ref"); char s [1000]; in >> s; in.close ();

The "stringified" IOR is now in the variable s. This can now be used to create an object of the type. To catch any errors during this process, a try-catch block is opened:

try {CORBA :: Object_var obj = orb -> string_to_object (s); IPBook_var f = IPBook :: _ narrow (obj);

With the object f of type that is now available, all operations can be carried out that were originally in the interface definition IPBook.idl have been established. The CORBA runtime system and our preparatory work now ensure that all on the object f (the Client stub, Stub = stub) automatically on the Servant, that means on the address range of the server program cabsrv_co lying object of the type are forwarded. Logically, the server program has to be started before the client can be called (there is the possibility to activate the server via the implementation repository on demand to be carried out, but this goes beyond the subject of this »Introduction« to CORBA).

So we now call the method with the two arguments and from the command line:

f -> addEntry (argv [1], argv [2]);

The rest of the program is dedicated to catching exceptions and displaying their cause:

} catch (CORBA :: SystemException_catch & ex) {ex -> _print (cerr); cerr << endl; rc = 1; } return rc; }

Second, we need a program to search for names in the phone book. The input

cabsearch_co Surname

should that too Surname Get the corresponding phone number from the server. The source code of the program (cabsearch_co.cpp) is given here without comment, with the knowledge gained so far, its interpretation should be possible without any problems:

#include #include "IPBook.h" #include #include usingnamespace std; int main (int argc, char ** argv) {CORBA :: ORB_var orb = CORBA :: ORB_init (argc, argv); int rc = 0; if (argc! = 2) {cerr << "usage:" << argv [0] << "name \ n"; exit (1); } ifstream in ("IPBook.ref"); char s [1000]; in >> s; in.close (); try {CORBA :: Object_var obj = orb -> string_to_object (s); IPBook_var f = IPBook :: _ narrow (obj); cout << f -> searchEntry (argv [1]) << endl; } catch (CORBA :: SystemException_catch & ex) {ex -> _print (cerr); cerr << endl; rc = 1; } return rc; }

Now the client programs can be translated and linked:

mico-c ++ -c cabadd_co.cpp mico-c ++ -c cabsearch_co.cpp mico-ld -o cabsearch_co cabsearch_co.o IPBook.o -lmico2.3.11 mico-ld -o cabadd_co cabadd_co.o IPBook.o -lmico2.3.11

Logically needs the servant object IPBook_impl.o in contrast to the server program, cannot be linked here.

Now we can test our first complete CORBA application:

$ cabsrv_co & $ cabadd_co Kurt 123 $ cabadd_co Heinz 445-566 $ cabsearch_co Kurt 123 $ cabsearch_co Moni ??? $ iordump One downer remains: so that the client and server can find each other, the object reference (IOR) of the servant had to be saved in a file. Both client and server programs need access to this file. As long as both programs are on one Computer is running, this shouldn't be a problem. But what if there are computer limits to be overcome? You might still be able to make do with setting up network drives (e.g. SAMBA or NFS shares), but you will soon come up against administrative limits. Therefore, methods are presented in the following two chapters to transport object references in a different way: The use of the proprietary MICO binder and the inclusion of the CORBA naming service.

The MICO binder

The CORBA implementation MICO provides the MICO-Binder as an aid for the localization of server objects. This is based on the following consideration: In order to be able to clearly identify and find a servant in a network, an IOR is not necessarily required. Alternatively, it is possible to designate a servant by its network address. In a TCP / IP network, network addresses consist of the IP address of the computer (or its name) and a port number. Since IP addresses and host names are assigned centrally, the worldwide uniqueness of the combination of IP address: port number is guaranteed. If a CORBA server program makes several objects available to the outside world, these can be differentiated by the type of servant (more precisely, by the repository ID of the interface definition). If there are several servants of the same type, the programmer has to give them a unique name (for each server program).

Since our telephone book server created in the penultimate chapter cabsrv_co only delivers a single CORBA object outside, the program can be used to use the MICO binder without any changes. You just have to ensure that the servant always uses the same port number when it starts. This is done by specifying the command line option. Without this option, the server would bind to any free port number each time it starts. The call

cabsrv_co -ORBIIOPAddr inet: munzel: 4500

thus causes the server to be on port 4500 of the computer munzel waiting for connections. Of course we have to munzel Replace with the host name of the computer on which our server is actually running. The host name can be changed with the command

hostname

determine.

In order to be able to use the MICO-Binder with our two clients, some changes to their source code are necessary. Essentially, the call from has to be replaced by. The address should be passed as a command line argument so that you can easily switch between different servers. The following is an example of the modified program cabadd_co.cppwhich we now cabadd_mi.cpp call, presented.

First, the necessary headers are included again and the test for the presence of the command line arguments is carried out. The address of the CORBA server must now be passed as the fourth argument:

#include #include "IPBook.h" #include usingnamespace std; int main (int argc, char ** argv) {CORBA :: ORB_var orb = CORBA :: ORB_init (argc, argv); int rc = 0; if (argc! = 4) {cerr << "usage:" << argv [0] << "name number server_address \ n"; exit (1); }

Now comes the crucial point: instead of using, it is called directly. The first parameter is the repository ID of our servant, the second the network address of the server transferred via the command line:

try {CORBA :: Object_var obj = orb -> bind ("IDL: IPBook: 1.0", argv [3]); if (CORBA :: is_nil (obj)) {cerr << "no object at" << argv [3] << "found. \ n"; exit (1); } IPBook_var f = IPBook :: _ narrow (obj);

If everything went well, we now have an object again with which all methods of the IPBook interface can be executed. The remainder of the program therefore does not bring anything new:

f -> addEntry (argv [1], argv [2]); } catch (CORBA :: SystemException_catch & ex) {ex -> _print (cerr); cerr << endl; rc = 1; } return rc; }

The changes to the program cabsearch are analogous and therefore understandable without a special description.

The programs are translated and linked as is known. If the server was bound to port 4500 as stated above, the call from

$ cabadd_mi Bimbo 123 inet: munzel: 4500 $ cabsearch_mi Bimbo inet: munzel: 4500

bring the expected output 123.

If you are in the fortunate position of having several computers connected via TCP / IP, you can now start the server and client on different computers. It only has to be guaranteed that the resolution of the host name munzel works on all participating computers.

The use of the MICO binder enables the user to dispense with the transport of object references and instead use network addresses to localize a CORBA server. This method should be perfectly adequate for most applications; however, it still has two disadvantages:

  • It is a proprietary extension of the CORBA standard. If there are objects in a distributed application that use another CORBA implementation instead of MICO, this method will not work.
  • If an application has to access a large number of CORBA objects, the administration of the many different port numbers can be complicated. After all, the information that, for example, port 4500 is to be used for communication must also be coordinated between the client and the server.

In these situations, the use of the CORBA naming service can help.

The CORBA Naming Service

The naming service is a standard CORBA service that is suitable for the simple administration of a large number of object references. It should be available in most implementations, as well as in the MICO. The naming service is a hierarchical directory in which it Naming contexts and Name entries gives. A naming context can be thought of as a kind of subdirectory. A name entry contains the IOR of a specific CORBA object. Both naming contexts and name entries have a symbolic name by means of which the object can be uniquely localized:

The client programs only need to know the name of the object they want to use and the address of the naming service. The latter can be transferred to the application via command line parameters.

However, the software developer has to make some modifications in the server and client application so that the object references can be resolved via the naming service. First, let's look at the server source code cabserv_ns.cpp at. The first lines up to the activation of the servant are almost identical to the already created program cabserv_co.cpp, it just needs to include the header file CosNaming.h are included:

#include #include #include "IPBook_impl.h" usingnamespace std; int main (int argc, char ** argv) {int rc = 0; try {// init ORB and POA Manager CORBA :: ORB_var orb = CORBA :: ORB_init (argc, argv); CORBA :: Object_var poaobj = orb-> resolve_initial_references ("RootPOA"); PortableServer :: POA_var poa = PortableServer :: POA :: _ narrow (poaobj); PortableServer :: POAManager_var mgr = poa-> the_POAManager (); // create a new instance of the servant IPBook_impl * impl = new IPBook_impl; // activate the servant IPBook_var f = impl -> _ this ();

Now the root context of the naming service has to be resolved. To do this, the object reference transferred to the naming service via the command line option is first obtained. This is then "narrowed down" to a NamingContext:

// resolve the naming service CORBA :: Object_var nsobj = orb-> resolve_initial_references ("NameService"); if (CORBA :: is_nil (nsobj)) {cerr << "can't resolve NameService \ n"; exit (1); } // narrow the root naming context CosNaming :: NamingContext_var nc = CosNaming :: NamingContext :: _ narrow (nsobj);

Based on this NamingContext (which is the RootNamingContext is), the entire hierarchy of the naming service can now be run through. In this simple example we want to limit ourselves to creating the simple name entry »AddressBook« directly. This is done using the function. However, this throws an exception if the entry already exists (from a previous run of the program). In this case the following must be used:

// create a name entry CosNaming :: Name name; name.length (1); name [0] .id = CORBA :: string_dup ("AddressBook"); name [0] .kind = CORBA :: string_dup (""); // bind or rebind the servant to the naming servicetry {nc-> bind (name, f); } catch (CosNaming :: NamingContext :: AlreadyBound_catch & ex) {nc-> rebind (name, f); }

The rest of the program runs as usual, only the code for handling exceptions has been expanded so that errors when specifying the address of the naming service are caught:

// activate POA manager mgr-> activate (); // run the ORB orb-> run (); poa-> destroy (TRUE, TRUE); delete impl; } catch (CORBA :: ORB :: InvalidName_catch & ex) {ex -> _ print (cerr); cerr << endl; cerr << "possible cause: can't locate Naming Service \ n"; rc = 1; } catch (CORBA :: SystemException_catch & ex) {ex -> _ print (cerr); cerr << endl; rc = 1; } return rc; }

When linking the program, the library with the access routines must be linked to the naming service.

The client's code cabadd_ns.cpp is to be adapted analogously to the use of the naming service, instead of or is called here in order to access the name entry created by the server:

#include #include #include "IPBook.h" #include usingnamespace std; int main (int argc, char ** argv) {// init ORB CORBA :: ORB_var orb = CORBA :: ORB_init (argc, argv); int rc = 0; if (argc! = 3) {cerr << "usage:" << argv [0] << "name number \ n"; exit (1); } try {// resolve the naming service CORBA :: Object_var nsobj = orb-> resolve_initial_references ("NameService"); if (CORBA :: is_nil (nsobj)) {cerr << "can't resolve NameService \ n"; exit (1); } // narrow the root naming context CosNaming :: NamingContext_var nc = CosNaming :: NamingContext :: _ narrow (nsobj); // create a name component CosNaming :: Name name; name.length (1); name [0] .id = CORBA :: string_dup ("AddressBook"); name [0] .kind = CORBA :: string_dup (""); // resolve the name component with the naming service CORBA :: Object_var obj = nc-> resolve (name); // narrow this object to IPBook IPBook_var f = IPBook :: _ narrow (obj); // work with IPBook f-> addEntry (argv [1], argv [2]); } catch (CORBA :: ORB :: InvalidName_catch & ex) {ex -> _ print (cerr); cerr << endl; cerr << "possible cause: can't locate Naming Service \ n"; rc = 1; } catch (CosNaming :: NamingContext :: NotFound_catch & ex) {cerr << "Name not found at Naming Service \ n"; rc = 1; } catch (CORBA :: SystemException_catch & ex) {ex -> _ print (cerr); cerr << endl; rc = 1; } return rc; }

Before the programs can be tried out, the CORBA (or MICO) naming service nsd must of course be started first. The familiar option is used so that it binds to a specific network port. The IANA has specified 2809 as the official port number for the CORBA naming server.

$ nsd -ORBIIOPAddr inet: munzel: 2809 & # munzel must be replaced again with the correct host name!

Now our server and client programs can go into action; However, you now always need the address of the (correct) naming service:

$ cabsrv_ns -ORBNamingAddr inet: munzel: 2809 & $ cabadd_ns Bimbo 123 -ORBNamingAddr inet: munzel: 2809 $ cabsearch_ns Bimbo -ORBNamingAddr inet: munzel: 2809 123

There is also the console program nsadmin for the administration of the naming service:

$ nsadmin -ORBNamingAddr inet: munzel: 2809 CosNaming administration tool for MICO (tm) nsadmin> ls AddressBook nsadmin> iordump AddressBook Repo Id: IDL: IPBook: 1.0 ... nsadmin> exit $

The advantage of using the naming service is only fully effective when a large number of CORBA objects of a »framework« are to be managed. Instead of working with a vast number of stringified IORs or network addresses, it is now sufficient to only provide all application components with the address of the naming service. With clever structuring using naming contexts, a large number of objects can be managed.

This brings us to the end of the CORBA tutorial. Of course, a lot could only be sketched out here or was left out completely. For more information, please refer to the following books:

Further books