Previous Up Next

Chapter 4  Portability of C++ CORBA Applications

The Portable Object Adapter (POA) specification defines a comprehensive set of APIs that are provided by CORBA products. This means that a developer should be able to write a CORBA application that can be re-compiled easily with several CORBA vendor products. This goal has been met with the Java mapping. Unfortunately, the C++ mapping has one annoying hindrance to portability: it does not specify the names of CORBA-related header files. The practical effect of this is that a developer must change #include directives for CORBA-related header files when porting an application to a different CORBA product. At first this may not seem like a big problem. However, such #include directives will appear in most source-code files. Porting an application is much easier if non-portable code is concentrated in just a small number of files rather than being spread thinly over many source-code files. This chapter discusses a simple, yet effective, technique that minimizes the porting headaches of non-portable #include directives.

4.1  Introduction to the Problem

The program below is quite simple: it creates an ORB (line 13), outputs "Hello, world" (line 14) and then destroys the ORB (line 15). A try-catch clause (lines 12–20) is used in case any of the CORBA APIs throws an exception.

1  #include <omg/orb.hh>
2  #include <it_cal/iostream.h>
3  IT_USING_NAMESPACE_STD
4
5  int
6  main(int argc, char ** argv)
7  {
8      CORBA::ORB_var orb;
9      int exit_status;
10
11     exit_status = 0;
12     try {
13         orb = CORBA::ORB_init(argc, argv);
14         cout << "Hello, world" << endl;
15         orb->destroy();
16     }
17     catch (const CORBA::Exception & ex) {
18         cout << ex << endl;
19         exit_status = 1;
20     }
21     return exit_status;
22 }

The functional code in the application (lines 5–22) is portable across many CORBA products and operating systems. Unfortunately, the #include directives in the code (lines 1–3) are not portable: these lines are specific to Orbix. It is these first few lines of code that would have to be changed if porting the application to another CORBA product. In general, there are three different portability problems associated with #include’d filenames for CORBA applications. These problems are discussed in the following subsections, and then a simple solution is discussed in Section 4.2.

4.1.1  Portability Problem 1: CORBA Header Files

The IDL-to-C++ mapping does not specify the names of CORBA-related header files. For example, Orbix defines basic CORBA functionality in the file <omg/orb.hh>, while Orbacus defines similar functionality in the file <OB/CORBA.h> and TAO uses <tao/corba.h>. In general, each CORBA product has a different name for this header file.

4.1.2  Portability Problem 2: Stub Code and Skeleton Code Header Files

If you have an IDL file called foo.idl then the Orbix stub-code and skeleton-code header files are called foo.hh and fooS.hh, respectively, while the equivalent header files in Orbacus are called foo.h and foo_skel.h, and the TAO versions are called fooC.h and fooS.h. In general, each CORBA product uses different names for the generated stub-code and skeleton-code files.

4.1.3  Portability Problem 3: Old or Standard C++ Header Files

Although, the C++ language dates from the early 1980s, the language was not standardized until the mid-1990s. In pre-standardized C++, many header files provided with compilers had ".h" extensions, for example <iostream.h>. The standardization committee decided to make two important changes to standard header files: (1) the ".h" extension was dropped, for example, <iostream.h> became <iostream>; and (2) the types and global variables defined in these standard header files were defined in the std namespace rather than in the global scope, for example, cout became std::cout.

The statement using "namespace std;" can be used to used to refer to std types and variables without the std:: prefix. If a developer wants to write a program that can be compiled with the old or the standard header files then this can be done using the somewhat clumsy coding idiom shown below:

#ifdef USE_OLD_HEADER_FILES
#include <iostream.h>
#else
#include <iostream>
using namespace std;
#endif

The developer must use similar #ifdef...#else...#endif constructs for all header files that have old and standard counterparts.

Many CORBA products have been developed so they can be used with old or standard header files. Developing a CORBA product with such portability in mind means extra work for a CORBA vendor. Typically it means that the CORBA vendor must provide two versions of libraries: one built with old header files and the other built with standard header files. This extra work undertaken by CORBA vendors has resulted in two important benefits. First, it allows a CORBA product to be made available on platforms that have only the old header files. Second, even on platforms that have the standard header files, some developers may be forced to use the old header files because they must link with legacy code that uses the old header files, and the CORBA product can be used by such developers.

It is common for CORBA products to provide their own abstraction layer that can be used to easily switch between using old and standard header files. For example, Orbix provides various header files with names such as <it_cal/iostream.h>, <it_cal/fstream.h> and so on.1 These files #include either the corresponding old or standard header file, depending on whether or not the symbol IT_CLASSIC_IOSTREAMS is defined. Also, depending on whether or not that symbol is defined, the macro IT_USING_NAMESPACE_STD (line 3 of the example program given in Section 4.1) expands out to be either an empty string or the statement "using namespace std;".

Developers can choose whether or not they want to make use of a CORBA products abstraction layer for old and standard header files. Obviously, the advantage of using this abstraction layer is that it is a pre-written abstraction layer so the developer does not have to re-invent the wheel. However, there are two disadvantages to using such an abstraction layer. First, the abstraction layer is proprietary to that CORBA product so use of it makes source code non-portable to other CORBA products. Second, making use of the abstraction layer typically involves #include-ing at least one CORBA product-specific header file into every .cpp file, and a developer may not wish to do this in a .cpp file that is otherwise independent of CORBA.

4.2  A Simple Solution

There is an easy way for developers to protect their source code from differences in the names of include files across CORBA vendor products and also, if desired, between old and standard C++ header files. The way to achieve this is for the developer to use his own portability abstraction layer. This is remarkably easy to do and takes very little time. This section discusses such a portability layer that supports Orbix, Orbacus, TAO and omniORB. It should be easy for readers to extend this to support other CORBA products on an as-needed basis. The principle is illustrated with file below.

// File: p_orb.h
#ifndef P_ORB_H_
#define P_ORB_H_

#if defined(P_USE_ORBIX)
#include <omg/orb.hh>
#elif defined(P_USE_ORBACUS)
#include <OB/CORBA.h>
#elif defined(P_USE_TAO)
#include <tao/corba.h>
#elif defined(P_USE_OMNIORB)
#include <omniORB4/CORBA.h>

#else
#error "You must #define P_USE_ORBIX, P_USE_ORBACUS, P_USE_TAO or ..."
#endif

#endif /* P_ORB_H_ */

The above file, p_orb.h, is an abstraction layer for the CORBA product-specific header file that defines basic ORB functionality. The "p_" prefix stands for portability. The header file #include’s the relevant Orbix-specific header file if the symbol P_USE_ORBIX is defined, the Orbacus-specific header file if P_USE_ORBACUS is defined, the TAO-specific header file if P_USE_TAO is defined, or the omniORB-specific header file if P_USE_OMNIORB is defined; otherwise it generates an error message. It should be trivial to extend this file to support other CORBA products. For most C++ compilers, the easiest way to define the appropriate P_USE_<product-name> symbol is through the -D<symbol> command-line option, for example, -DP_USE_ORBIX.

Another header file that should be written in the same style is p_poa.h, which #include’s the CORBA product-specific header file that defines the POA APIs.

Portability header files that #include CORBA product-specific stub-code and skeleton-code header files also need to be written. For an IDL file, foo.idl, these portability header files might be called, say, p_foo_stub.h and p_foo_skeleton.h. The two files below illustrate the general form of these portability header files. Occurrences of foo and FOO are written in bold to indicate which parts of the files depend on the name of the IDL file.

// File: p_foo_stub.h
#ifndef P_FOO_STUB_H
#define P_FOO_STUB_H
#if defined(P_USE_ORBIX)
#include "foo.hh"
#elif defined(P_USE_ORBACUS)
#include <OB/CORBA.h>
#include "foo.h"
#elif defined(P_USE_TAO)
#include "fooC.h"
#elif defined(P_USE_OMNIORB)
#include "foo.hh"
#else
#error "You must #define P_USE_ORBIX, P_USE_ORBACUS, P_USE_TAO or ..."
#endif
#endif /* P_FOO_STUB_H */

// File: p_foo_skeleton.h
#ifndef P_FOO_SKELETON_H
#define P_FOO_SKELETON_H
#if defined(P_USE_ORBIX)
#include "fooS.hh"
#elif defined(P_USE_ORBACUS)
#include <OB/CORBA.h>
#include "foo_skel.h"
#elif defined(P_USE_TAO)
#include "fooS.h"
#elif defined(P_USE_OMNIORB)
#include "foo.hh"
#else
#error "You must #define P_USE_ORBIX, P_USE_ORBACUS, P_USE_TAO or ..."
#endif
#endif /* P_FOO_SKELETON_H */

The file templates shown above support Orbix, Orbacus, TAO and omniORB. It should be trivial to extend them to support other CORBA products. Because these files are so repetitive and they need to be written for each IDL file used in a project, it is best to write a short script (using Tcl, Perl, sed or whatever scripting language you prefer) that can generate these portability header files.

Finally, portability header files can be written that #include the appropriate old or standard C++ header files. This is illustrated by example below:

#ifndef P_IOSTREAM_H_
#define P_IOSTREAM_H_
#if defined(P_USE_OLD_TYPES)
#include <iostream.h>
#else
#include <iostream>
using namespace std;
#endif
#endif /* P_IOSTREAM_H_ */

As the above example illustrates, if the symbol P_USE_OLD_TYPES is defined then the appropriate old header file is included; otherwise, the standard header file is included and the statement "using namespace std;" is executed so programmers do not have to use the std:: prefix.

4.3  Issues not Tackled

This chapter has discussed how a simple technique can dramatically reduce problems in porting applications to use different CORBA products. Some other portability issues exist that are outside the scope of this chapter, such as:


1
The "it_cal" prefix is derived as follows: "it" is an acronym for IONA Technologies, and "cal" is an acronym for Compiler Abstraction Layer.

Previous Up Next