The CORBA (Common Object Request Broker Architecture)
standard was developed by the Object Management Group, a membership
organization based in Framingham, Massachusetts. From its beginnings in 1989
with eleven member organizations, the OMG has grown to over 800 member
companies. At its most basic, CORBA is a way for objects to interoperate across
a network.
The CORBA specification v1.1 was introduced in 1991 to give
a standard mechanism for objects to communicate across a network. The
specification has evolved since that time, with the introduction of CORBA 2.0
in 1994 and CORBA 3.0 soon to be released in 2000. Its important to note that
CORBA is a specification, not a product. Vendors wishing to create products
that follow the spec are free to do so, but must ensure that they follow the
spec precisely in order to claim to be CORBA compliant. Its because of this
adherence to a written standard that various compliant products from different
vendors are able to interoperate. The standards called The Common Object
Request Broker: Architecture and Specification and is available from the OMG
for a nominal cost, or available as a free download from their web site. The OMG can be reached at http://www.omg.org.
One important thing to remember is a CORBA is language
independent. As long as a mapping exists from the CORBA interface definition
language specification to the language you program in, you will be able to
write to CORBA applications in the language, and they will be able to
communicate with CORBA applications in any other language.
In essence, CORBA defines a standard way for objects to talk
to each other without you having to write the code for that communication to
happen.
Well, lets assume that you were sold on the business case
for object orientation. You understand the need for thorough object-oriented
analysis and design, youre concerned that implementation takes too long.
If thats your concern, youre right. Software development
takes too long and costs too much. Typically, each project starts from scratch,
which makes large applications that model the enterprise extremely cumbersome
to build and costly to maintain.
But the problems with distributed objects dont end there.
Its incredibly difficult to get the hardware computers and networks to
work together. We cant always count on homogenous systems in our networks
since the different platforms have different strengths. Unfortunately, the
truth is that different platforms dont work and play well together in a
heterogeneous system.
As difficult as it is to get hardware to work together,
software can be worse. An enterprise system today is a collection of diverse
parts, working together toward a common goal. It costs time and money to
integrate the disparate parts into a whole, and unfortunately too much effort t
in getting the parts to talk to each other, taking energy away from the problem
at hand.
CORBA helps in this interoperability dilemma by providing a
specification that third parties can adhere to and, if they follow the rules
correctly, assure that applications built using their libraries will
interoperate.
While CORBA is an excellent accomplishment, it only connects
objects, not applications. Enterprise integration requires a lot more than
this, and the OMG provides it with the Object Management Architecture (OMA).
Based on CORBA, the OMA specifies a set of standard interfaces and functions
for components. Its divided into two major components: lower-level
CORBAservices and intermediate-level CORBAfacilities.
CORBAservices provide basic functionality that almost any
object might need, such as object lifecycle services, naming and directory
services, and other basics. These can be thought of as horizontal
CORBAservices touch all different kinds of CORBA applications. In contrast,
CORBAfacilities can be thought of as vertical specifications central to an
industry, such as health care, finance, etc., or central to a type of
programming task, such as user interface, information or systems management, or
task management.
Visibroker is a CORBA 2.3 compliant object request broker
(or ORB), with a suite of tools thrown in. If youre not sure about what
an ORB is, dont worry; well be talking more about that as we go on.
Visibroker for C++ includes libraries and headers that allow you to write
client and server programs that can communicate with each other across a
network, without having to deal with the complicated code underlying that
communication. If you have a TCP/IP network, Visibroker (like any other
CORBA-compliant ORB product) contains all the functionality needed to get your
programs to talk to each other.
What exactly is CORBA and what are the various components
that make up the CORBA client/server pair? The diagram below may help
illustrate how communication happens between a CORBA client and CORBA server.

The ORB (object request broker) and POA (portable object adapter)
are, for our purposes, C++ objects that you instantiate in your code. They come
from the Visibroker libraries that ship with the Visibroker installation and
are essentially black boxes that you can use without totally understanding
their underlying complexity. From the server side, you write the code for your
worker objects (shown in blue). These objects interface with the POA (via
generated skeleton code) and the POA interfaces with the ORB so that the ORB
may communicate to the client. As you can see from the diagram, the ORBs
purpose in life is to provide a communication mechanism between applications.
The skeleton and stub code in the diagram is
automatically generated from your object model by tools supplied by the vendor.
From the clients view of the world, the client has a
reference to an object that it got from the ORB. In C++, this reference
translates to a wholly separate object type that knows it should communicate
with the object being referenced on the server. This is quite convenient from
the clients perspective, since the client application has no idea that the
object its referencing lives on another computer. In fact, CORBA objects are
location transparent. This means that the client and server applications may or
may not be same computer, in fact they may be on different computers in the
network or may actually live in the same box. For that matter, the client and
server applications may share the same application space; there is nothing in
the specification that tells you how you must partition the client and server
code.
Visibroker has optimizations that kick in when the client
and server share the same application space. If the client and server code
share the same process space, calls from the client to the server are made as direct
function calls. If the client and server are different processes in the same
computer, client-to-server calls are treated as remote procedure calls. It is
only when the client and server run on different computers into the network
that TCP/IP packets are sent from application to application.
CORBA provides mechanisms for getting references to objects
that live somewhere on the network without necessarily knowing exactly where
they live. In fact, they may not even be available at the time the client asks
for them. The mechanism of creating an object that doesnt exist is called
object activation. We wont be talking about object activation here, so from
here on in assumed that any contract referenced from a client already exists on
the server.
Client-side references maintain their life span via
reference counting. Unfortunately, its up to you as the client application
programmer to ensure that when a reference is no longer used, its reference
count drops to zero. Each class thats generated contains a _duplicate and
_release function for incrementing and decrementing its objects reference
counts. In practice, though, this can be an incredibly daunting task, so the
CORBA specification outlines the requirements for smart pointer objects that
need to be generated along with the C++ translations of the interfaces. Well
be talking more about these later in the section entitled <class_name>_var
and <class_name>_ptr types.
At the heart of creating CORBA applications is the CORBA
interface definition language.
Interface definition language (or IDL) contains a full set of variable
types that map to many different languages. As of July 1999, language mappings
existed for Ada, C, C++, Cobol, Java, and Smalltalk. Well concern ourselves
with the C++ mapping here.
Chapter 3 of the CORBA specification outlines the syntax of
IDL. IDL contains syntactical elements
that allow for both attributes and operations to be specified. For our purposes, we can think of attributes
as a member of variables in a class and operations map directly to C++
functions. CORBA does not offer every C++ variable type, and thats primarily
due to it being a universal language. Of special note is that pointers are not
available in CORBA IDL since not every language offers direct access to
pointers. A lot of thought went into the types of variables that would be
available in CORBA IDL with the goal of interoperability in mind.
A program that ships with Visibroker, idl2cpp.exe, is used
to come to compile CORBA IDL into C++ code. The code thats generated can be
used for both client and server applications.
Interface definition language is used to describe the
interfaces that client objects calling and servers provide. And interface
definition written in IDL completely defines the interface and fully specifies
each operations parameters. We dont actually write programs in IDL, but use
IDL to define the classes of objects that we will be using in our programs.
IDL is very similar to C++, down to preprocessing features.
It was written with C++ in mind, and according to the CORBA specification, the
OMG IDL specification is expected to track relevant changes to C++ introduced
by the ANSI standardization effort.
Files containing interface specifications written in IDL
must have an . idl extension. While the formal grammar of IDL uses Extended
Backus-Naur Format (EBNF), well discuss its syntax in English. Those of you
wishing to view the EBNF are referred to the CORBA specification, chapter 3.
The name of an interface in IDL is similar to the name of a
class in C++. But the name changes when we use the interface at runtime. The
formal description of an interface name uses the following structure:
IDL:[Module[/Module]/]Name:Version
Where IDL: is required and module names, if present, are
listed separated by a / character. The name of the interface comes next, and
is separated from the version of the interface by a :. The first version of
an interface is, by default, 1.0, and is the version number that will be
created IDL compiler unless otherwise specified.
An IDL file looks a lot like a C++ header file, with a few
syntactical differences. In the section below entitled IDL
to C++ mappings, well discuss how IDL keywords map to C++ keywords. But
since IDL was designed, for the most part, from C++, its not unusual that
well see incredible similarities in their structure.
Lets take a look at a prototypical IDL file.
module
BorCon2K {
interface Person {
attribute string firstName;
attribute string lastName;
string FullName(in boolean
lastNameFirst);
};
interface Time {
attribute unsigned short hour;
attribute unsigned short
minute;
};
interface Date {
attribute unsigned short
month;
attribute unsigned short day;
short DayOfConference();
};
enum eRoom {
roomA, roomB, roomC, roomD
};
interface Presentation {
attribute Person speaker;
attribute Time
presentationTime;
attribute Date
presentationDate;
attribute eRoom
presentationRoom;
attribute string topic;
float GetPreviousRating(inout
short year);
};
interface Tutorial : Presentation
{
attribute boolean onCD;
};
typedef sequence<Presentation>
PresentationSeq;
};
Just from looking at the above you can, as a C++ programmer,
probably figure out what its trying to do. The module
declaration looks suspiciously like a namespace, and indeed thats what it maps
to in C++. The interface declarations look like they have member variables
and functions, therefore theyre probably classes. Right again interfaces map
directly to classes in C++.
A few differences between IDL and C++ need discussed,
though. Some are pretty obvious, some subtle.
Youll notice the attribute keyword used throughout the
interface definitions. Thats a required keyword when youre declaring member
variables. You also may have noticed that certain C++ types look subtly
different, like boolean instead of bool.
Operations (functions) also have modifiers on their
parameters. In the example above, you may have noticed in and inout. While
we didnt use it, another modifier is out; these argument modifiers clue the
ORB on how to package parameters. Parameters that are in wont be modified,
while inout or out parameters are expected to be modified by the called
function. An inout parameter has a value that the caller has set that the
function will use and then modify, while an out parameter has no useful
information that the caller has set, but will have useful information in it on
return from the function.
One subtle difference is a lack of visibility specifiers.
All operations in IDL are presumed to be public, as are all inheritances.
Attributes will have public accessor and mutator (i.e., get and set) functions
created for them, and it will be up to the implementation to actually create
the variables that hold that data, if theyre there at all. BCB programmers can
think of attributes as properties: theyre accessed via functions and may or
may not actually have 1-for-1 correlations with data members that store the
information those functions get and set.
IDL to C++ mappings
Lets look at the various mappings between IDL and C++.
|
IDL type
|
WIN32 C++
definition
|
Visibroker
type
|
|
short
|
short
|
CORBA::Short
|
|
long
|
long
|
CORBA::Long
|
|
unsigned
short
|
unsigned
short
|
CORBA::UShort
|
|
unsigned
long
|
unsigned
long
|
CORBA::ULong
|
|
float
|
float
|
CORBA::Float
|
|
double
|
double
|
CORBA::Double
|
|
char
|
char
|
CORBA::Char
|
|
boolean
|
unsigned
char
|
CORBA::Boolean
|
|
octet
|
unsigned
char
|
CORBA::Octet
|
|
long long
|
VISLongLong
|
CORBA::LongLong
|
|
ulong long
|
VISULongLong
|
CORBA::ULongLong
|
One thing youll immediately note is that in the second
column, Visibroker type, each attribute type is prefixed with CORBA::. This
is because with Visibroker creates a namespace called CORBA and defines all
CORBA functions and typedefs in that namespace. Interestingly enough, there is
no integer type. Integers are divided into either shorts or longs. Again, this
is due to CORBAs desire to be language independent. IDL also offers two types
that are not available in standard C++: long long and unsigned long long. Since
they are not present in C++, they are implemented in Visibroker as the classes
VISLongLong and VISULongLong.
Its also important to note that the IDL boolean type is
defined by CORBA to have only one of two values: 1 or 0. Using other values for
a Boolean will result in the undefined behavior. You would think that the
Boolean type would map to the C++ bool type directly, but remember that the
specification for C++ mapping was written long before the Boolean type was
added to the language.
The CORBA specification states that an IDL module
maps to a C++ namespace. By default, though, Visibroker maps the module keyword
to a C++ class, which is not in compliance with the standard. This is done
because older compilers may not necessarily support the namespace keyword. In
Visibroker 4.0 a switch to the C++ code generator idl2cpp (the -namespace
switch) can be used to cause a module to generate a namespace.
Modules can be used quite effectively to separate class
names from class names that could have been created by other programmers. The
designers of IDL realized that the problem C++ programmers were running into
with namespace clashes would certainly be a problem in IDL. The module keyword
was created to help alleviate that problem.
Interface specifications can contain constants, attributes,
operations, and typedefs. An interface maps directly to a C++ class, and
contains attributes and operations specific to that class. There are no visibility specifiers in
interfaces, instead, all attributes and operations are presumed to be
public. Should you need protected or
private members of the C++ class, youll define them in the implementation
class.
Attributes in interfaces generate accessor and mutator
functions that have defined signatures.
For example, in attribute defined as
attribute string someString;
would generate two functions:
void
_set_someString(const char* _someString);
const char* _get_someString();
In normal practice, you wont use
these functions. The generated client-side code will create two alternate
functions for use by the client:
void someString(const char* _someString);
const char* someString();
When a client calls these functions,
the client-side generated the stub code packages the parameter (if applicable)
and the ORB that was instantiated by the client communicates with the ORB on
the server. The server-side ORB calls a
servant object whose purpose in life is to communicate with the
implementation object you wrote in the server application. Its in the servant object where the _get_
and _set_ functions are defined.
For the most part, unless youre
writing applications that take advantage of the Dynamic Invocation Interface,
you wont need to worry about the _get_ or _set_ functions. The Dynamic
Invocation Interface specification is beyond what were going to cover in this
document for more information see the CORBA specification.
The IDL keyword attribute is used to preface member
variables. Operations are specified much in the same way that they would be
specified in C++, but arguments must be specified as in, out, or inout.
This argument specification is required to help the communications functions
determine how to package the arguments for sending between the client and the
server. As you might guess, sending a unit of information with no expectation
that it will be modified is vastly different than sending a piece of data that
need be changed and that change must be noted by the sender.
CORBA IDL supports string objects directly. Strings are
specified in IDL using the keyword string, and optionally modified by
specifying the maximum length for the string using angle brackets, as in
attribute
string<36> aString;
Here, the variable aString
is restricted to 36 characters. This maps to a character pointer (char*) in
C++, and since character pointers dont contain length information, the
36-character restriction is discarded information. Other languages, though,
could use the restriction when their code is being generated.
Allocating and freeing string storage space in CORBA clients
and servers is a bit problematic. Since a string could be returned from a
server to a client, or passed from a client to a server, and thus need to be
shared between process spaces, the CORBA libraries provide specific functions
to allocate and free string memory. The functions CORBA::string_alloc
and CORBA::string_free should be used for string
parameters passed between CORBA-compliant programs. CORBA::string_dup
is also available as a quick way of allocating and setting CORBA allocated
memory by using quoted strings or other char* types, as in
some_function_that_wants_a_string(
CORBA::string_dup(some
string));
CORBA IDL offers the concept of the constant, much in the
same way that C++ has constants. In fact, IDL was modeled very closely after
C++, so its no surprise that much of its syntax looks quite a bit like C++.
Using the const keyword, as in the following, specifies a constant:
const long l = 100;
Constants can be specified at any level, either inside or
outside an interface (class) definition.
Since IDL was closely modeled after C++, its no surprise
that enumerations (enum) and type definitions (typedef) are also
available. The syntax for these two are identical to C++.
Structures
An IDL struct maps directly to a C++ struct in much
the same way that an interface maps to a class. The difference here is that a
struct may not contain operations, which is a departure from C++. A struct in
CORBA is identical to a struct in C.
Other mappings from IDL to C++ are available, including
arrays, values, and containers. For our introductory talk, though, youve got
more than enough to get started. More information can be found at the OMG web
site, http://www.omg.org/.
The IDL2CPP compiler generates a number of types for you.
Lets discuss some of those.
The <interface_name> class is generated for a
particular IDL interface, intended for use my client applications. This class
provides all the stub methods defined for a particular IDL interface. When a
client calls an operation on a server-side object through an object reference,
the stub methods are actually invoked. These stub methods package the
parameters for the operation, send them to the object implementation, and
unpackage the results upon return. This entire process is transparent to the
client application. Note that you should never modify the contents of a
stub class generated by the IDL compiler.
The _POA_<class_name>
class is an abstract base class generated by the IDL compiler that is used as a
base class for your implementation class. Object implementations are usually
derived from this type of servant class, which provides the necessary methods
for receiving and interpreting client operation requests.
The _sk_<class_name> is generated if you use the -boa option to IDL2CPP, as is the default for IDE-based
CORBA servers. Both of these classes help satisfy the requirement that all
object implementations derive from CORBA::Object.
It became clear to the
developers of CORBA thats not every programmer would want to restructure their
object inheritance hierarchy to derive every class from CORBA::Object. The _tie_<class_name> class is generated by the IDL compiler to help in
those cases.
The tie class serves as a wrapper
around your implementation class. It duplicates all functions in your
implementation class and holds a pointer to an object of your type. A tie
object derives from CORBA::Object, satisfying the inheritance requirement, and
all client requests are passed to the tie object. Since this object holds a
pointer to your implementation object, it calls your object with the parameters
passed to it, and forwards your return value back to the client.
<class_name>_var and
<class_name>_ptr types
For any given interface type, a_var and a _ptr type will be
generated. The _ptr type is a typedef that can be used in place of a
<class_name>*, and the_var type is a class that an capsulate a smart
pointer for references to <class_name>.
You are strongly encouraged to use the smart pointer types
whenever possible. Since the CORBA references use reference counting to
determine when they should be deleted, using the smart pointers reduces the
headaches youll have to face in trying to keep the reference counts accurate.
Sequences
A sequence is a C++ class thats generated from the
following IDL:
typedef sequence<someType>
sequenceName;
where someType is an IDL type (either a built-in type
or a predefined interface) and sequenceName is the name you've given to
this new sequence type. This class is a container class, and can hold either a
defined number of objects or an undefined number (i.e., be either bounded
or unbounded).
Lets look, for example, at sequence of long integers. The IDL would for that would look like the
following:
typedef sequence<long> LongSeq;
Note that youre required to typedef the sequence in order
for the IDL2CPP compiler to generate a class name for the sequence.
The C++ class is generated from this code looks like the
following:
class
LongSeq {
private:
CORBA::Long* _contents;
CORBA::ULong _count;
CORBA::ULong _num_allocated;
CORBA::Long _ref_count;
CORBA::Boolean _release_flag;
public:
static CORBA::Long *allocbuf(CORBA::ULong
_nelems);
static void freebuf(CORBA::Long *_data);
LongSeq(CORBA::ULong _max=0);
LongSeq(CORBA::ULong _max,
CORBA::ULong _len,
CORBA::Long *_data,
CORBA::Boolean _release=0);
LongSeq(const LongSeq&);
~LongSeq();
LongSeq& operator=(const LongSeq&);
CORBA::ULong maximum() const { return
_num_allocated; }
void length(CORBA::ULong _len);
CORBA::ULong length() const { return
_count;}
CORBA::Long& operator[](CORBA::ULong
_index) ;
const CORBA::Long&
operator[](CORBA::ULong _index) const;
friend VISostream&
operator<<(VISostream&, const LongSeq&);
inline friend VISostream&
operator<<(VISostream& _strm,
const LongSeq *_obj) {
if ( _obj == (LongSeq*)NULL )
throw CORBA::BAD_PARAM();
else
_strm << *_obj;
return _strm;
}
friend VISistream&
operator>>(VISistream&, LongSeq&);
inline friend VISistream&
operator>>(VISistream& _strm,
LongSeq_ptr & _obj) {
_obj = new LongSeq;
_strm >> *_obj;
return _strm;
}
friend Ostream& operator<<(Ostream&,
const LongSeq&);
inline friend Istream&
operator>>(Istream& _strm, LongSeq& _obj) {
VISistream _istrm(_strm);
_istrm >> _obj;
return _strm;
}
inline friend Istream&
operator>>(Istream& _strm,
LongSeq_ptr & _obj) {
VISistream _istrm(_strm);
_istrm >> _obj;
return _strm;
}
static LongSeq *_duplicate(LongSeq* _ptr) {
if (_ptr) _ptr->_ref_count++;
return _ptr;
}
static void _release(LongSeq *_ptr) {
if (_ptr && (
--_ptr->_ref_count == 0) ) delete _ptr;
}
};
One of the nice things about the sequence function is that
it overloads the [] operator, giving you random access to the contents of the
sequence. Adding an element to sequence
is a simple matter of assigning an object to the sequence using these
operators, as in
LongSeq_var mySequence =
new LongSeq;
mySequence[0] = 70L;
mySequence[1] = 100L;
Its important to note that when creating a sequence, youre
required to specify the length of the sequence it doesnt do it
automatically. So in order to finish
off the above code, we would need to add the line
mySequence.length(2);
so that when other code queries the sequence forgets length,
it will return the appropriate value.
CORBA servers are the workforce applications of the
client/server model. Typically, the CORBA server is written as a console mode
application, since it does not usually require a pretty user interface. Its
the client applications that typically contain a GUI front-end since theyll
need to be accessed by regular users. The servers the application that sits
behind locked doors in the computer room, silently chugging away and providing
the objects to the clients when they request them.
Borland C++Builders wizards allow for quick creation of
CORBA server code from IDL. You dont need to use the wizard to create a
server, but it sure makes life easier. Lets take a look. The following will
work in either version 4 or version 5 of C++Builder Enterprise.
Open up BCB and select File/New and, on the multitier tab,
click CORBA Server. The window you see should look something like the
following:

After clicking on CORBA server, you should see the following
dialog:

Here is where you select whether your application will be a
console app or a Windows app. For our purposes in this demonstration lets
create a console mode application for the server. Just click OK without adding
any IDL files at this time. You should see following code opened in your editor
window.
//--------------------------------------------------------------------
1 #include
<corbapch.h>
2 #pragma
hdrstop
//--------------------------------------------------------------------
3 #include
<corba.h>
4 #include
<condefs.h>
5 #pragma
argsused
6 main(int
argc, char* argv[])
7 {
8 try
9 {
10
// Initialize the ORB and BOA
11
CORBA::ORB_var orb = CORBA::ORB_init(argc, argv);
12
CORBA::BOA_var boa = orb->BOA_init(argc, argv);
13
// Wait for incoming requests
14
boa->impl_is_ready();
15 }
16
catch(const CORBA::Exception& e)
17 {
18
Cerr << e << endl;
19
return(1);
20 }
21
return 0;
22 }
//--------------------------------------------------------------------
Lets take a look at what we got.
Line 1 includes an optimized set of precompiled headers for
CORBA work. These are included here rather than down in line 3 since they will
be used for any type of CORBA application, from clients to servers to
extensions.
Now look at line 11. Heres where the fun begins. In line 11
were creating a reference to the ORB, the object that will be used to
communicate to our client applications. Well be calling the function
CORBA::ORB_init(int
argc, char** argv)
which passes in argc and argv from main, in case we started
the server with any ORB-specific parameters. You probably wont need any
parameters, but theyre there in case you need them.
The reference to the ORB is stored in an ORB_var, one of the
smart pointer generated types we talked about before.
In line 12, we call a function off the ORB to get a
reference to a BOA. The BOA (basic object adaptor) is the object that comes
from the Visibroker libraries and serves to connect our objects to the ORB.
Well be storing the reference to the BOA in the smart pointer variable as
well.
We should take a moment to note that the BOA space is no
longer used in the CORBA 2.3. The object that connects user objects to the ORB
is the POA, or portable object adapter. The BOA is a relic from a previous
version of CORBA, but is implemented in with Visibroker 4.0 as a wrapper around
the POA. In reality, discussing the POA is more than we want to go into here.
The BOA is a much simpler object, and since Visibroker implements it correctly
for CORBA 2.3, well continue to use the BOA in our code. To get the IDL
compiler to generate code that uses the BOA, we need to pass the -boa to it at IDL compilation time. In
the project options dialog, this flag is passed by default. If you change this
default value, you will need to manually change the code that the wizard
generates for your application to use a POA.
The comment on line 13 tells us exactly whats going to
happen next: line of 14 waits for incoming requests. Unfortunately, although
our server will work, it doesnt do anything. We havent created an object for
our clients to link to. Lets do that now.
From the main menu, choose File/New and click on the
multitier tab. This time select CORBA IDL File and click OK. Youll get a
new blank file with an .idl extension. Enter the following code:
interface foo {
attribute string s1;
};
Now save the file and youll see, by looking at the project
manager window, that its been added to the project. Right-click on it there
and choose Compile.
If everything goes right now, youll have two more files
added to your project. If you saved the IDL file as file1.idl, the new files
will be named file1_c.cpp and file1_s.cpp. Lets talk about these two for a
moment.
The _c file contains code that should be compiled into the
client application. The _s file contains code for the server. Due to the
structure of the inheritance hierarchy, the client code is necessary for the
server side application as well. So youll need to compile both of these files
into your server application.
The code generated by IDL2CPP should never been modified
directly by you. If you make changes to your IDL file, recompile the IDL and
new generated files will be created.
Youll also see header files that have been generated for both
the client and server side modules. C++Builders wizard will include the
headers in the correct modules for you.
Now that we have the source code generated for our object,
we needed to make the object available to the client by instantiating it before
the client asks for a reference to it. The easiest place to do this is in main,
right before we wait for incoming requests.
Again, choose File/New from the main menu but this time, on
the multitier tab, choose CORBA object implementation. You should see a
dialog that looks something like this:

This dialog gives you a choice of IDL files that are in your
project. After choosing an IDL file from the first combo box, the interfaces
listed in that IDL file will be available for you in the second combo box. Our
project only has one IDL file, so choosing that will show us that foo is
the only interface available in that file. After selecting the foo
interface in the second combo box, we get a dialog that looks something like
this:

As you can see, the implementation class name and unit name
(i.e., the name of the .cpp file) has been filled in for you. At the bottom of
this dialog, in the instantiation section, were given the option to create
an object in the main function. By default that is checked, and since we want
to instantiate an object in main, well leave it like that. Click OK.
Youll see now look back in another file, fooServer.cpp, has
been added to the project. Lets take a look at the contents of that file.
//--------------------------------------------------------------------
#include
<corbapch.h>
#pragma
hdrstop
#include
<corba.h>
#include
"fooServer.h"
//--------------------------------------------------------------------
#pragma
package(smart_init)
fooImpl::fooImpl(const
char *object_name):
_sk_foo(object_name)
{
}
char*
fooImpl::s1()
{
}
void
fooImpl::s1(const char* _s1)
{
}
Since we only had one attribute in this very simple interface,
only a few functions were generated for us. A constructor was generated for the
class fooImpl to give us a chance to do any setup work for the object.
Youll see that the implementation object were defined, fooImpl, is derived
from some code generated by IDL2CPP. This class, _sk_foo, is called a skeleton
class. Its derived from the foo class that the client-side code will
use to access our object across the network. The parameter to the constructor
allows us to name this object so that clients may access it by name. This name
isnt a part of the CORBA standard, but a Visibroker extension to the standard.
(Extensions are allowed under the standard, as long as the entire specification
in the standard is followed.) Well talk more about named objects later when we
build our client.
In this example we dont need to do anything so well leave
the constructor empty. Lets take a look at the two accessor functions that
were created for our attribute.
The CORBA specification indicates that accessor (i.e., get
functions) and mutators (i.e., set functions) should be named the same as the
attribute specified in the IDL. Get functions return the type of the attribute,
and set functions return void but take one argument which is of the same type
as the attribute. Our attribute, s1, was a string attribute, and as we
have discussed previously a string maps to a C++ char*.
The body of the get and set functions are left blank since,
obviously, the code generator has no idea what we want to do with these
functions. We need to add implementation code, so lets do that now.
Change the two functions to look like the following:
char* fooImpl::s1()
{
static int counter = 0;
char buffer[100];
sprintf(buffer, string %d, counter++));
return CORBA::string_dup(buffer);
}
void
fooImpl::s1(const char* _s1)
{
cout << Client tried to set s1 to
<< _s1 << endl;
CORBA::string_free(_s1);
}
Remember that, in order to use strings, we need to allocate and
a free memory using the supplied CORBA string allocation functions. Thats why
we didnt simply allocate the memory using new and free the memory with
the delete operator.
Looking back into our main module, youll see that the code
in the try block has changed as well. It now looks like
// Initialize the ORB and BOA
CORBA::ORB_var orb = CORBA::ORB_init(argc,
argv);
CORBA::BOA_var boa = orb->BOA_init(argc,
argv);
fooImpl foo_fooObject("fooObject");
boa->obj_is_ready(&foo_fooObject);
// Wait for incoming requests
boa->impl_is_ready();
Our fooImpl objector has been instantiated, and a
call to boa->obj_is_ready has been acted prior the code which waits for incoming requests.
That function call, off the BOA, register is our object with the BOA. Now a request
from the client transmitted to our object through the BOA.
The call to boa->impl_is_ready causes the server
to block waiting for calls from clients. If for some reason we decide that the
server should terminate, it will be required to call the function boa->shutdown
to cause execution to return to the main function and continue after that
function call. As you can see from the code, this will cause main to exit.
Our server is ready to go. It only provides one object, and
a pretty skimpy object at that. But, what the heck, its a working server.
Lets create a client.
Save your server project and lets create a new client
project.
Choose File/New and, on the multitier tab, select