t e m p o r a l 
 d o o r w a y 

A Basic CORBA Application

 

Introduction

CORBA (the Common Object Request Broker Architecture) is a standard of the Object Management Group (OMG), a consortium of organizations responsible for the creation and maintenance of this cross-platform distributed system specification. Visibroker, a product of Inprise, the Borland parent company, is an implementation of the CORBA standard which runs under Windows 9x, Windows NT and Unix. C++ Builder Enterprise Edition offers experts which greatly simplify the creation of distributed applications that use CORBA, and, since it comes with Visibroker, Visibroker is the typical Object Request Broker (ORB) used by C++ Builder developers.

CORBA applications consist of clients and servers. Servers implement services used by clients, and a server may be a client to another server. Servers are typically multithreaded, and can also have separate instances running on more than one network node, each of which has its own threads to service requests from clients.

The Uses Of CORBA and Distributed Programming

CORBA, like any distributed programming technique, is only useful when you have a problem which would benefit from the simultaneous application of multiple CPUs, usually over a network. These problems typically have one or more of the following special requirements:

  • Parallel computations on separate CPUs are needed to attain high performance.

  • Scalability to a large user population or a high-transaction volume requires the dynamic addition of computational resources.

  • Fault-tolerance in the face of CPU or network failures requires the ability to migrate capabilities to accessible computing resources.

How CORBA Works

CORBA aids in solving such problems by providing an abstraction of multiple independent objects residing on one or more network nodes (determining the location of the objects is performed by the ORB, and the actual location of the object used is generally not known to the client). These objects appear to communicate by standard class method invocations, but CORBA actually puts the arguments to a method call into one or more network packets (called marshaling), and sends them to the object, which is typically executing on another CPU. The object, a multithreaded executable, unpacks the request (called unmarshaling), and creates or reuses an internal thread to perform the request. That thread may set up results to be returned to the client, or may throw a CORBA exception. In either case, the results or exception are marshaled and sent to the client, which then either gets the result or experiences the exception as a conventional C++ exception of a specific type (standard CORBA or programmer-defined).

Because CORBA objects can exist on separate nodes in the network, and because those nodes may have different CPU architectures or operating systems, the marshaling / unmarshaling process translates values to the appropriate format for the destination. For instance, some CPUs use integers with the high order bit to the left, others use integers with high order bits to the right. CORBA handles these issues without programmer intervention, but does require the programmer to create a platform / language independent specification of the object interface, in an Interface Description Language (IDL) that is fairly close to C++. The IDL description is used to create header files that establish the interface for C++ client and server executables.

The Example Application

The example is a very simple subsection of a distributed ray tracer. A ray tracer is a program which creates an image based on geometric descriptions of physical objects and their properties using the laws of optics on simulated light rays cast into the scene. Because ray tracing is so computationally intensive, and because many ray-tracing calculations can proceed in parallel, it is a natural for a CORBA implementation. Note however that the subset shown is very small and does not involve the performance or merging of parallel computations (that will be left for a future article).

The application does use a client server database, which makes it essentially a three-tier application (client, server objects, DBMS).

The portion of the application shown is a client and an object which maintains a database table of "spaces" within which objects to be raytraced can reside. Currently, this is a very simple two attribute table, with a space ID and a space name. The server object is called "multiverse", and supports adding, removing, getting the names of all spaces, getting the name of a space based on its ID and getting the ID of a space based on its name.

The Project Group and Projects

The Project Group contains two projects - RayTracer (the client) and Multiverse (the server object).

The Multiverse Object

First to be created is the Multiverse project. To create this project, first one creates the IDL file which describes the object interface, using File | New | Multitier | CORBA IDL File. The following is the text added to the IDL file:

module Multiverse
{
    interface SpaceRegistry
    {
         exception SpaceNotFound{};

         unsigned long AddSpace(in string theSpaceName);
         void DeleteSpace(in unsigned long theSpaceID) raises (SpaceNotFound);
         string NameOf(in unsigned long theSpaceID) raises (SpaceNotFound);
         unsigned long IDOf(in string theSpaceName) raises (SpaceNotFound);

         typedef sequence<string> NameList;

         NameList Spaces();
    };
};

As you can see, there are some similarities to C++. "module" is essentially a namespace declaration, "interface" is essentially a declaration of the object class, "exception" defines an exception which can be thrown to the client, and the rest involves methods and type definitions available to users of the object. Note that method definitions identify the exceptions that they can throw and that method arguments can be in, out or inout, depending on whether they are from the client, values returned to the client or both. Note also that methods can be functions (that is, can return a value).

Note that the Visibroker documentation does not call attention to the need for a module declaration, but it is not a thing you want to add later (for instance after you have generated the header files from the IDL).

Naming Things In The IDL File

There are probably many approaches to naming objects in a CORBA IDL. In this case, all interfaces will reside in the Multiverse module, simply because the multiverse contains the space registry and the spaces which it references. Other parts of the application may reside in different modules, which will be determined later.

Creating The Server Project

The IDL file is then saved and is used as the basis for the server. This is done with File | New | Multitier | CORBA Server. This server is a Windows application and uses the IDL file created above. The result is a project with a form, and the project source contains a couple of calls to initialize the ORB and the Basic Object Adapter (BOA) which handles activation of the server and registering the objects with the ORB.

At this point, the Multiverse server project contains the IDL, but the object represented by the IDL has not been made available to the server. First, compile the IDL, which creates the C++ header files that represent the server interface. If you are lucky, you will never ever have to look at these things, which rival ActiveX header files in their nasty appearance and undocumented complexity - but you must have them available.

The act of compilation adds two units to your project. These are the _c and _s .cpp files. Once again, there should be no need to look at these in any detail.

Save the project at this point.

Hooking The IDL To The Server

Next, you must set up the implementation of the interface described by the IDL. There are two ways to do this. One is to have the IDE generate a class which inherits from the IDL-based classes in the generated header files. But the most powerful method, and the method which allows for the easiest use of VCL components in your server, is to use "delegation".

To set up for delegation, with the IDL selected in your project manager, pick File | New | CORBA Object Implementation. If you have not picked the IDL in the project manager, the IDE may appear to stop working until you repeatedly hit the ESC key, in which case you should select the IDL and repeat the action.

A dialog should appear immediately, showing the IDL file path. Pick the interface name from the combo (in this case there is only one). Check off the "Delegation (Tie)" and "Data Module" check boxes. You can change the implementation class name from the default (for instance SpaceRegistryImpl to SpaceRegistryImplementation, if you happen to like spelling things out (I do)).

The result of this after hitting OK will be a data module with a header and .cpp file containing empty method bodies to implement the interface. In this case it looks like

//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop

#include <corba.h>
#include "SpaceRegistryServer.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TSpaceRegistryImplementation *SpaceRegistryImplementation;
//---------------------------------------------------------------------------
__fastcall TSpaceRegistryImplementation::TSpaceRegistryImplementation(TComponent* Owner)
: TDataModule(Owner)
{
}
//---------------------------------------------------------------------------

char* __fastcall TSpaceRegistryImplementation::NameOf(CORBA::ULong theSpaceID)
{
}

CORBA::ULong __fastcall TSpaceRegistryImplementation::AddSpace(const char* theSpaceName)
{
}

CORBA::ULong __fastcall TSpaceRegistryImplementation::IDOf(const char* theSpaceName)
{
}

Multiverse::SpaceRegistry::NameList* __fastcall TSpaceRegistryImplementation::Spaces()
{
}

void __fastcall TSpaceRegistryImplementation::DeleteSpace(CORBA::ULong theSpaceID)
{
}

You can reformat the code as needed and add any additional class methods, properties or variables you desire. Obviously, you also need to implement the interface.

Implementing The Interface

All CORBA servers are multithreaded Therefore it is important to protect against simultaneous access to shared memory or resources. The easiest way to do this is to use a CORBA mutex to create critical sections in your implementation methods. First, add the mutex to the implementation data module class definition private section:

VISMutex SpaceRegistryInstance;

Then at the top of each method implementation (this may be overkill, but is certainly safest) use the mutex:

VISMutex_var Lock(SpaceRegistryInstance);

The mutex will be automatically released when it goes out of scope (on return from the method or if an exception is thrown).

Another issue is throwing exceptions. This is handled in a fairly simple fashion, as illustrated by the implementation of the IDOf method:

VISMutex_var Lock(SpaceRegistryInstance);

MultiverseMainForm->LogMemo->Lines->Add("IDOf");

if (Table->Locate("NAME",theSpaceName,TLocateOptions()))
{
    return TableID->AsInteger;
}
else
{
    throw Multiverse::SpaceRegistry::SpaceNotFound();
};

A final issue is working with databases. Since the object is multithreaded, you must have a TSession and TDatabase object specifically placed in the implementation data module. The following .dfm shows the components used (the database is an Interbase database). The user name and password are hard coded to avoid any prompting on object activation.

object SpaceRegistryImpl: TSpaceRegistryImpl
   OldCreateOrder = False
   Left = 3
   Top = 343
   Height = 289
   Width = 371
   object Source: TDataSource
      DataSet = Table
      Left = 96
      Top = 176
   end
   object Table: TQuery
      DatabaseName = 'SpaceRegistryImplDatabase'
      SessionName = 'Session1_1'
      RequestLive = True
      SQL.Strings = (
         'SELECT *   FROM MULTIVERSE ORDER BY NAME')
      ValidateWithMask = True
      Left = 96
      Top = 104
      object TableID: TIntegerField
         DisplayWidth = 10
         FieldName = 'ID'
         Origin = 'MULTIVERSE.ID'
      end
      object TableNAME: TStringField
         DisplayLabel = 'Name'
         DisplayWidth = 80
         FieldName = 'NAME'
         Origin = 'MULTIVERSE.NAME'
         Size = 255
      end
   end
   object IDSource: TQuery
      DatabaseName = 'SpaceRegistryImplDatabase'
      SessionName = 'Session1_1'
      RequestLive = True
      SQL.Strings = (
         
            'SELECT TABLENAME,NEXTID FROM IDSOURCE WHERE TABLENAME = "MULTIVE' +
            'RSE"')
      ValidateWithMask = True
      Left = 216
      Top = 104
      object IDSourceNEXTID: TIntegerField
         FieldName = 'NEXTID'
         Origin = 'IDSOURCE.NEXTID'
      end
      object IDSourceTABLENAME: TStringField
         FieldName = 'TABLENAME'
         Origin = 'IDSOURCE.TABLENAME'
         Size = 255
      end
   end
   object Database: TDatabase
      AliasName = 'RayTrace'
      Connected = True
      DatabaseName = 'SpaceRegistryImplDatabase'
      LoginPrompt = False
      Params.Strings = (
         'USER NAME=multiverse'
         'PASSWORD=keytospace')
      SessionName = 'Session1_1'
      Left = 96
      Top = 16
   end
   object Session1: TSession
      Active = True
      AutoSessionName = True
      Left = 208
      Top = 16
   end
end

After completing the implementation, compile and link the project. You can (as I have) use the form to report the actions of the server. This is not required, and you can use logging or other strategies (or no strategy) for keeping track of requests and responses. My form shows both the database in a grid and each method logs a message to a TMemo on the form.

The Client

Creating the client requires a new project. File | New | Multitier | CORBA Client is the way to get started. Make it a Windows app and use the same IDL as the server.

Next, add a data module to the project. This will be the client's view of the server object. With the IDL selected in your project manager, pick Edit | Use CORBA Object. If you have not picked the IDL in the project manager, the IDE may appear to stop working until you repeatedly hit the ESC key, in which case you should select the IDL and repeat the action.

Pick the interface you wish to access from its combo in the dialog. Pick the data module you just created from the Use In Form page combo. Enter an object name which is the same as the name you can find in the project source for the server (in the line Multiverse_tie_SpaceRegistry tie_spaceRegistry_SpaceRegistryObject(*spaceRegistry_SpaceRegistryObject, "SpaceRegistryObject", true); "SpaceRegistryObject" is the object name), and change the Use In Form property name to the same as the name of the interface (by default it gets a lower case initial letter, which can be changed).

The result is a set of class variables, methods and properties. The implementation of the property getter for the SpaceRegistry will bind to the object if it has not yet been bound and will return the interface class, which can then be used quite naturally by the client UI, for instance in a statement like

try
{
   MultiverseData->SpaceRegistry->AddSpace(SpaceNameEdit->Text.c_str());
}
catch (const CORBA::SystemException &CorbaException)
{
   Application->MessageBox((char *) CorbaException._name(),"Add Space Click",0L);
};

Note the exception catch, and that the string must be passed to the method as a char *. The cast on the exception makes the message non-const, which is required by Application->MessageBox.

Once your client implementation is complete, compile and link its project. Then you are ready to start testing.

Testing Your Client And Server

There are three things you need to set up before starting local testing. First, the Object Activation Daemon needs to be run on your machine. This is done by running oad.exe from the command line, Windows Explorer or a Start Menu entry. Next, register the server with the OAD, with a batch file or command line like

oadutil reg -r IDL:Multiverse/SpaceRegistry:1.0 -o SpaceRegistryObject -cpp g:\develop\cbuilder4\test\corba\raytracer\multiverse.exe -a SpaceRegistryObject -p shared

This command registers the server so that the OAD can find it when the object is requested (the object name is the -o parameter), and identifies how it should be activated. In this instance, it is activated as a shared server, which means it will be started only once and will not be terminated by the OAD. If it were unshared, a separate instance would be started for each request, and then terminated when the request is completed. Shared servers are best for frequent requests and for local testing. Unshared is best for infrequent requests, where the overhead of startup is not as important.

Finally, run the Smart Agent so that the OAD can be found by the client.

Now you are ready to test. If you are running under Windows NT, you can run both the client and the server from your project group undr debugger control in the IDE. If you are running Windows 9x, you must pick one or the other to run from the IDE and run the other from a command line or Windows Explorer.

It is usually best to start by running the client from the IDE. This lets you make sure that the client side is OK before trying to debug the server. Note that the default is for the client to wait forever for a response from the server, so if the server fails or has a problem, or cannot be found (for instance because the Smart Agent isn't running, you may need to reset the client from the debugger.

At this point, if the server is throwing an exception or otherwise experiencing a problem, but the client seems OK, you can run the server from the IDE and the client from the command line or the Explorer. In this case, you can set breakpoints etc. as needed, and calls to the server will be trapped by them.

Conclusion

Once you've completed a successful local test, you can use remote debugging or other strategies to perform remote testing across the network.

In any event, remember that a good distributed application will minimize network traffic and maximize the use of multiple CPUs.

And have fun with CORBA!

Copyright © 2004 by Mark Cashman (unless otherwise indicated), All Rights Reserved