Where Developers Matter
Integrated Development Environments for Windows, Java, and Web Developers
| | ログオン

Introduction to CORBA with VisiBroker and C++Builder

投稿者: : Michael Sawczyn

概要: An introductory paper for those familiar with C++ and C++Builder and wanting to learn about CORBA and how to create, use, and deploy CORBA systems.

Introduction

Whats CORBA?

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.

Ok. So whats the big deal?

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.

How does Visibroker fit into this?

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.

A Birds Eye View of CORBA

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.

IDL Syntax

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 naming of interfaces

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.

A lot like C++

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.

Modules

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.

Interfaces

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.

Attributes and Operations

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.

Strings

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));

Constants

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.

Enumerations and Typedefs

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 types

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/.

IDL2CPP generated types

The IDL2CPP compiler generates a number of types for you. Lets discuss some of those.

<interface_name> class

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.

_POA_<class_name> and _sk_<class_name> classes

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.

_tie_<class_name> class

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.

Instantiating an object implementation

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.

Writing attribute accessors and mutators

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.