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:
- There will be differences in Makefiles
when porting an application from one CORBA product to another.
For example, flags passed to the IDL compiler and C++ compiler
will change. Other changes will occur in the names of libraries
that should be linked into the application. Also, the names of
the generated stub-code and skeleton-code .cpp files
will differ.
- A C++ CORBA product is typically
intended to be used with a specific brand of C++ compiler. If
you switch from one CORBA product to another then you might
also have to switch to a different brand of C++ compiler.
- Although the CORBA specification
describes the high-level functionality of the Implementation
Repository (IMR), the CORBA specification has not standardized
the “look and feel” of the IMR. For this reason,
details of how to register a server with the IMR are different
in different CORBA products.
- CORBA has not standardized upon APIs for
logging diagnostic messages or for retrieving runtime
configuration information. Many CORBA products provide logging
and configuration APIs as proprietary enhancements. If you make
use of such proprietary APIs in your application then this will
make porting your application to another CORBA product more
difficult.
- 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.