Introduction to CORBA using C++Builder and VisiBroker

By: David Intersimone

Abstract: This paper shows you how to use C++Builder and VisiBroker to build distributed object applications. It will cover what CORBA is, how it works, and the steps for building a CORBA based application.

Introduction to CORBA

Rabi Satter - Apres Technology Group, Inc.

Note: The following paper was presented at the 1998 Inprise/Borland Conference in Denver Colorado. Click on the source code link to download the examples uses in this paper.

Introduction

This paper shows you how to use C++Builder and VisiBroker to build distributed object applications. It will cover what CORBA is, how it works, and the steps for building a CORBA based application.

 

CORBA

Common Object Request Broker Architecture (CORBA) is a technology standard developed by the Object Management Group (OMG[www.omg.org]), a non-profit group, to allow objects to communicate with each other and develop the architecture to support remote objects.  It consists of standards for an Object Request Broker (ORB) and the services and facilities for supporting a distributed application.

 

The ORB defines a mechanism for objects to communicate with each.  You can think of the ORB as an object bus which one can plug objects into to communicate, regardless of location and language the object was written. In addition the mechanism for communication is transparent to the programmer. The programmer uses the remote object the same way one would use a regular object. The client object views the remote object as local and the remote object views the client object as local.

 

The CORBA services represent services that distributed object systems may need.   These services are used by the remote and local objects to work with the ORB and other objects.

 

Service Description
Naming Serviced used to locate other objects by a name.
Externalization Standard way for retrieving and saving information using streams
Persistence Standard way for storing objects
Events Standard way for registering and un-registering objects interested in an event. The event object is responsible for notifying event recipients
Life Cycle Defines the life cycle of objects on the object bus
Transactions Defines a two-phase commit mechanism for objects
Properties Service for associating properties with an object
Query Standard for querying objects using a scripting language. Similar to SQL and based on SQL 3 specification and Object Database Management Group Object Query Language.
Concurrency Control Provides a locking mechanism for objects.
Relationships Allows for creating links between objects that have no direct knowledge of each other.
Collection Provides a standard for creating and manipulating collections of objects
Time Standard for providing synchronizing time in a distributed environment
Security Framework for security on an object
Startup Automatic activation service to start objects not yet started
Trader Directory of available objects
Licensing Standard for watching the usage of an object

 

CORBA facilities provides frameworks and standards for building applications. By specifying standards for applications companies can build applications that interact with outside agencies. Computer departments and software vendors can produce standard applications that can be mixed and matched.

 

CORBA has several benefits over traditional two, three, and N-tier client/server systems.

  • The programmer uses a familiar method for using a remote object
  • Can find, query and invoke methods of a remote object dynamically
  • Using interfaces one can invoke a method without knowledge of the actual behavior
  • CORBA objects can be written in a variety of languages including C++, COBOL, Java, Ada, Smalltalk, and Object Pascal
  • Integration of legacy systems can be accomplished by hiding them behind an interface
  • CORBA allows objects to seamlessly interact with each other without regard to the location, language, or platform
  • CORBA can scale from a stand alone palmtop to the enterprise

 

How does CORBA work?

To understand how CORBA works lets first stop and look at how objects interact with each other. The heart of object interactivity is the message. A message is composed of four pieces: the target object, method name, parameters for the method and a return value. Parameters and a return value are optional. In C++ a message looks like this:

vendor1.setCompanyName("Apres Technology Group, Inc.");

 

Essentially the message tells vendor1 to execute its setCompanyName method passing in the parameter of "Apres Technology Group, Inc.". If  the vendor1 object has a setCompanyName method that accepts a string as a parameter it will respond to the message. In order to have an object communicate with vendor1 remotely you just have to have a mechanism for sending the message to another process either on the same or remote computer.

 

It would be nice if the message passing was hidden from the programmer making the remote object call transparent. In investigating how objects works with messages you find that objects are composed of two parts, the interface and the implementation (see Object Technology: A Manager's Guide by David A. Taylor, Ph.D.). An interface is a set of messages an object will respond. It is very similar to the definition of an object except it is composed only of the messages or methods the underlying object will accept. This allows for an object to call another object and execute a method of that object with out actually knowing the underlying implementation. This ability to send messages to an object regardless of the implementation is what allows for polymorphism.

 

C++ allows for defining an interface using an abstract class. An object can then inherit from one or more abstract classes. In addition an object can be reference by its abstract superclass. For example, suppose a HouseContract object is derived from a Contract abstract class. Then anyone who references an Contract class object could interact with the object completely ignoring other methods the object may implement. A new type of contract can be easily created and integrated into the application by simple deriving a new contract object.

 

So what do interfaces have to do with CORBA?  CORBA uses the separation of interface and implementation to build a framework of underlying objects to pass the actual message. The programmer first specifies an interface. Next, defines the implementation of the methods defined by the interface. The complexity of passing messages is hidden from the programmer by inheriting from a basic object that encapsulates the communication with the object. Using this approach the programmer just has to worry about the actual method implementation rather than communication between objects.

 

CORBA specifies a language called Interface Definition Language, IDL, for specifying an interface independent of the language or platform. The IDL is then translated into a target language and a framework of objects are created to allow two objects to talk with each other regardless of their location or language. The heart of the framework is the stub and skeleton objects. In the figure below the calling object interacts with a stub object that implements the interface of the remote object. The stub object bundles the method name and parameters into a message that can be sent over a network using a proprietary message standard or Internet InterOperable Protocol, IIOP, to a receiving object called the skeleton. The skeleton object then unbundles and calls the remote object. The remote object has no idea that it is being called remotely. The client object has no idea that it is calling a remote object. The stub and skeleton objects act as an object bus to seamlessly connect the two objects over the network.

 

 
 

Building a CORBA application

To actually build and run an application using CORBA the following steps are required:

  1. Define the interface using IDL
  2. Convert IDL to C++ using IDL2CPP tool
  3. Implement the actual object
  4. Build a server
  5. Build your client
  6. Run OSAgent
  7. Run the server
  8. Run the client

The following sections will go through each step in depth and is based on a simple customer object.

 

Building an Interface

The first step is to build an interface consisting of the methods that can be invoked remotely. IDL is used to define the interface of an object system regardless of the target platform or language. By using a language neutral interface language one can generate the code needed for each target platform. This allows an object to communicate with any other object regardless of the platform or language it was written.

 

IDL allows you to create several elements including:

  • Modules - A module allow you to group related interfaces together.
  • Interfaces - An interface is a set of attributes and operations that a remote object provides to calling objects.
  • Attributes - An attribute is a formal description o f a property with get and set operations.
  • Operations - An operation is a method can be executed by an object.  The operation includes parameters, returns and exceptions.
  • Complex Types - User defined data types can be created including array, sequence, struct, enum, union, typedef, any.  Additionally the next standard of CORBA v3.0 will allow for passing objects by value.
  • Exceptions - User defined exception that is passed from the remote object to the client.

An IDL module allows for grouping related interfaces, modules and other information together.  It maps to a class that contains the rest of the module.  A module statement consists of the key word module followed by a valid identifier.  The rest of the module is contain between { };.  A module statement is optional and mainly used to aid in grouping related interfaces and definitions together.

Syntax of Module

[module identifier {

};]

[Note: Syntax consists of bold for keyword and [] for optional parts]

 

Listing of IDL for Example Module
module NewModule {
  interface Class1 {
  .....
  }
  interface Class2 {
  .....
  }
}

 

Listing of C++ for Example Module
class NewModule {
  class Class1 : public virtual CORBA_Object {
  ....
  }

  class Class2 : public virtual CORBA_Object {
  ....
  }
....
}

An IDL interface defines the interface that client objects see.  An IDL interface is composed of operations and attributes. The interface may also contain user defined types and exceptions. An interface can be inherit the operations and attributes of another interface. When the IDL is converted to C++, the class created includes the attributes and methods.  In addition, it contains additional methods and datatypes.  These additional methods are what drive the message passing and allow for dynamic discovery and invocation of operations.

 

Syntax of Interface

interface identifier [: interface] {

};

 

Listing of the IDL for customer
interface customer {
  attribute string name;
  attribute string address;
  attribute string city;
  attribute string state;
  attribute string zip;
  double getBalance();
};

 

Listing of the C++ for customer
class customer : public virtual CORBA_Object {
private:
  static const CORBA::TypeInfo _class_info;
  customer(const customer&){ _root = this; }
  void operator=(const customer&){}

protected:
  customer_ptr _root;
  void set_root(customer_ptr root) {
    _root = root;
  }
  
public:
  static  const CORBA::TypeInfo *_desc();
  virtual const CORBA::TypeInfo *_type_info() const;
  virtual void *_safe_narrow(const CORBA::TypeInfo& ) const;
  static CORBA::Object *_factory();
  customer_ptr _this();
protected:
  customer(const char *obj_name = NULL): CORBA_Object(obj_name, 1) { _root = this; }
public:
  virtual ~customer() {}
  static customer_ptr _duplicate(customer_ptr _obj) {
    if ( _obj ) _obj->_ref();
    return _obj;
  }
  static customer_ptr _nil() { return (customer_ptr)NULL; }
  static customer_ptr _narrow(CORBA::Object *_obj);
  static customer_ptr _clone(customer_ptr _obj) {
    CORBA::Object_var _obj_var(__clone(_obj));
#if defined(_HPCC_BUG)
    return _narrow(_obj_var.operator CORBA::Object_ptr());
#else
    return _narrow(_obj_var);
#endif
  }

  static customer_ptr _bind(
      const char *_object_name = NULL,
      const char *_host_name = NULL,
      const CORBA::BindOptions* _opt = NULL,
      CORBA::ORB_ptr _orb = NULL);

  virtual char* address();
  virtual void address(const char* _val);

  virtual char* city();
  virtual void city(const char* _val);

  virtual char* state();
  virtual void state(const char* _val);

  virtual char* name();
  virtual void name(const char* _val);

  virtual char* zip();
  virtual void zip(const char* _val);

  virtual CORBA::Double getBalance(
      );

  friend VISistream& operator>>(VISistream& _strm, customer_ptr& _obj);
  friend VISostream& operator<<(VISostream& _strm, const customer_ptr _obj);
  friend Ostream& operator<<(Ostream& _strm, const customer_ptr _obj) {
    _strm << (CORBA::Object_ptr)_obj;
    return _strm;
  }

  friend Istream& operator>>(Istream& _strm, customer_ptr& _obj) {
    VISistream _istrm(_strm);
    _istrm >> _obj;
    return _strm;
  }

};


Attributes are used to define properties of an object. An attribute consists of a type and name.  In C++ it maps to get and set methods of the same name as the attribute. An attribute can also be read only and generate only the get method. To make an attribute read only use the key word readonly before the attribute key word.

 

Syntax of Attribute

[readonly] attribute type identifier;

 

Example of Attributes in IDL
attribute string name;
readonly attribute double balance;

 

Example of Attributes mapped to C++
public void name(string name);
public string name();
public double balance();

 

Operations are methods that a client object can execute.  The operation consists of parameters, a return, and exceptions. Parameters consists of a mode, type and parameter name.  The mode specifies the direction a value is passed either in from the calling object, out to the calling object or both directions using inout. An operation is defined just like a method in C++ the return type followed by the operation name.  Next is the list of parameters and lastly any defined exceptions the class may throw. 

 

Syntax of Operation

type identifier([type param, ...]) [raises (exception)];

 

Example of Operations in IDL
void MakePayment(in double amount, in string currency);
double BalanceDue(in long custno, out boolean pastdue);
void isValidAddress(in string address) raises(NotValidAddress);
Example of Operations mapped to C++
virtual CORBA::Double balanceDue(
      CORBA::Long _custno,
      CORBA::Boolean& _pastdue
      );

virtual void makePayment(
      CORBA::Double _amount,
      const char* _currency
      );
virtual void isValidAddress(
      const char* _address
      );

 

IDL has a full range of primitive and complex data types.  The primitive data types include boolean, character, string, octets, integers, and floating point.   Complex data types include array, sequence, struct, enum, union, typedef, any.  The any type is a self describing data type that retains it type.  You use any to load or extract IDL predefined types in a type safe and generic manner.

 

Basic data types mapped to C++

Data Types

IN

INOUT

OUT

Return

short
short
short&
short&
short
long long long& long& long
unsigned short unsigned short unsigned short& unsigned short& unsigned short
unsigned long unsigned long unsigned long& unsigned long& unsigned long
float float float& float& float
double double double& double& double
boolean boolean boolean& boolean& boolean
char char char& char& char
wchar wchar wchar& wchar& wchar

 

The last major item that can be described in IDL is an exception. Two main categories of exceptions exist in CORBA, system and user defined.  The exception mechanism allows remote objects to throw exceptions on the client side.  System exceptions are descendents of SystemException and consist of a major and minor error code along with the message.  User defined exception are derived from UserException.  To define an exception use the keyword exception and then define variables that are held by the exception.

 

Syntax of Exception

exception identifier {
  [type identifier]
};

 

Example of Exception in IDL
exception InvalidAddress {
  string message;
  short  errorLevel;
};
Example of Exception mapped to C++
class InvalidAddress : public CORBA_UserException {
public:
#if defined(MSVCNEWDLL_BUG)
  void *operator new(size_t ts);
  void operator delete(void *p);
#endif
  static const CORBA_Exception::Description _description;
  CORBA::String_var message;
  CORBA::Short errorLevel;
  
  InvalidAddress() {}
  InvalidAddress(
      const char * _message,
      CORBA::Short _errorLevel) {
    message = _message;
    errorLevel = _errorLevel;
  }
  static CORBA::Exception *_factory() { return new InvalidAddress(); }
  ~InvalidAddress() {}
  virtual const CORBA_Exception::Description& _desc() const;

  static InvalidAddress *_narrow(CORBA::Exception *_exc);
  CORBA::Exception *_deep_copy() const { return new InvalidAddress(*this); }
  void _throw() const {
    throw *this;
  }

  void _write(VISostream&) const;
  void _copy(VISistream&);
  void _pretty_print(VISostream&) const;

  inline friend VISistream& operator>>(VISistream& _strm, InvalidAddress& _e) {
    CORBA::String_var _exp_name;
    _strm >> _exp_name;
    _e._copy(_strm);
    return _strm;
  }
};


To create an IDL mapping file you start by declaring either a module or interface. Next define the operations and attributes of an interface. Below is an example of an IDL for a customer interface.

 

Listing of the interface for customer
interface Customer {
  attribute string name;
  attribute string address;
  attribute string city;
  attribute string state;
  attribute string zip;
  void nextRec();
  void priorRec();
};

IDL2CPP Tool

Once the IDL is created, the IDL must be mapped to C++ using a C++ mapping tool.   The mapping tool creates the code to C++ classes needed to communicate and implement the object. In addition to the stub and skeleton classes several helper classes are created. These helper classes are used to assist in communicating and data type conversion functions.

 

Using C++Builder 3 you can create a project that is a bat file.  Next bring up project options which will allow you to edit the bat file. You want to make the batch file the first project that is "compiled".  When the compiler detects the batch file rather than compile the batch file it executes it.

 

Screen shot of Batch File Options

410B.GIF (7236 bytes)

 

Use the following command in the bat file:

idl2cpp -src_suffix cpp -hdr_suffix h -no_tie customer.idl

idl2cpp is the command used by Visigenic to take an IDL file and convert it to the objects used to communicate between the client object and server object.   Two parameters that are useful are -src_suffix and -hdr_suffix which will change the suffix from the default c and hh to cpp and h.  The -no_tie option turns off generating the tie files.  The tie option allows you to create a server object without using inheritance which is the standard way of creating a server object.  The implementation of the actual methods are then delegated to an object at run-time. The tie approach will not be discussed in this paper however it is a viable option using Visigenic ORB to have existing objects turned into remote objects without changing the existing object.  Check the Visigenic documentation or the Inprise (www.inprise.com) website for more information on the tie method.

At this point you will have one group project and a bat file as a project when you open the project manager.

 

Screen shot of Project Manager:

410C.GIF (4938 bytes)

 

Implementing the Interface in a Remote Object

Step three is building the object that actually implements the interface. The object is built by inheriting from _sk_<interface>. The skeleton class actually implements the communication protocol between the client and remote object. The skeleton and stub classes marshal and un-marshal the message sent over the network.  In addition the object includes methods for querying and invoking methods dynamically at run-time. This introspective ability is used in conjunction with the Interface Repository subsystem and allows for finding and interacting with remote objects without prior knowledge of their existence or operations. Due to the generated classes you only need to implement the methods and attributes of the interface.

 

To build the remote object you can use a Library project. A library project is a collection of units to compile but not link.  Select File|New and then choose the Library project.  Then add the customer_s. and customer_c.cpp files to the project.  Finally create a unit to add the code implementing the customer.

 

One of the most important things in this process is to understand what happens with the parameters on the implementation object.  This is especially true of strings.  A string can not be directly received or passed back to the client.  The string passed or received using CORBA::string_dup(_val) to create a new instance. In addition the implementation object is responsible for freeing the string by using CORBA::string_free(_val).  The rest of the primitive types can be passed directly between the client and server.

 

In the example a datamodule with a TQuery is created for each instance of the customer object.  The query selects all the customers from the IBLocal demo database. The client object then uses the attributes to receive and return values to the fields of the current record.  To change records the client uses the the nextRec or priorRec operations which move the record pointer in the query using the next and prior methods. Additional methods to insert and update the query could be added using the same format as the nextRec and priorRec operations.

 

In addition to coding the actual methods defined by the IDL, a constructor that receives a string is created. This constructor binds the remote object with the ORB Naming service. The name allows client objects to look up an object by its name in a directory. The directory contains the name of an object and its location on the network.

 

Listing of the implementation for the customer interface (Note: listing represents just the implementation)
char* CustomerImpl::name() {
return  CORBA::string_dup(dtMdl->qryCustomerCUSTOMER->Text.c_str());
}
//---------------------------------------------------------------------------
void CustomerImpl::name(const char* _val) {
  if (dtMdl->qryCustomer->State != dsEdit) {
     dtMdl->qryCustomer->Edit();
  }
  dtMdl->qryCustomerCUSTOMER->Text = CORBA::string_dup(_val);
  CORBA::string_free(_val);
}
//---------------------------------------------------------------------------
char* CustomerImpl::address() {
return  CORBA::string_dup(dtMdl->qryCustomerADDRESS_LINE1->Text.c_str());
}
//---------------------------------------------------------------------------
void CustomerImpl::address(const char* _val) {
  if (dtMdl->qryCustomer->State != dsEdit) {
     dtMdl->qryCustomer->Edit();
  }
  dtMdl->qryCustomerADDRESS_LINE1->Text = CORBA::string_dup(_val);
  CORBA::string_free(_val);
}
//---------------------------------------------------------------------------
char* CustomerImpl::city() {
return  CORBA::string_dup(dtMdl->qryCustomerCITY->Text.c_str());
}
//---------------------------------------------------------------------------
void CustomerImpl::city(const char* _val) {
  if (dtMdl->qryCustomer->State != dsEdit) {
     dtMdl->qryCustomer->Edit();
  }
  dtMdl->qryCustomerCITY->Text = CORBA::string_dup(_val);
  CORBA::string_free(_val);
}
//---------------------------------------------------------------------------
char* CustomerImpl::state() {
return  CORBA::string_dup(dtMdl->qryCustomerSTATE_PROVINCE->Text.c_str());
}
//---------------------------------------------------------------------------
void CustomerImpl::state(const char* _val) {
  if (dtMdl->qryCustomer->State != dsEdit) {
     dtMdl->qryCustomer->Edit();
  }
  dtMdl->qryCustomerSTATE_PROVINCE->Text = CORBA::string_dup(_val);
  CORBA::string_free(_val);
}
//---------------------------------------------------------------------------
char* CustomerImpl::zip() {
return  CORBA::string_dup(dtMdl->qryCustomerPOSTAL_CODE->Text.c_str());
}
//---------------------------------------------------------------------------
void CustomerImpl::zip(const char* _val) {
  if (dtMdl->qryCustomer->State != dsEdit) {
     dtMdl->qryCustomer->Edit();
  }
  dtMdl->qryCustomerPOSTAL_CODE->Text = CORBA::string_dup(_val);
  CORBA::string_free(_val);
}
//---------------------------------------------------------------------------
void CustomerImpl::nextRec() {
  dtMdl->qryCustomer->Next();
}
//---------------------------------------------------------------------------
void CustomerImpl::priorRec() {
  dtMdl->qryCustomer->Prior();
}
//---------------------------------------------------------------------------

Before you compile the project you need to make some changes to the include and lib directory options. First, you need to add c:visigenicvbrokerinclude to the list of includes. Second, you need to add c:visigenicvbrokerlib to the list of libraries.

 

Screen shot of include directories

Directory Options

 

At this point you will have one group project and two projects.  The first is the bat file to compile the IDL and the second is the library project that will compile the remote object implementation.

 

Screen shot of Project Manager:

Project Screen

 

Building the Server Application

The next step is to build the server. The server houses the remote object and infrastructure required for listening and processing client messages. The server can be built in a variety of ways including as a NT Service, console application or multithreaded GUI application.  In this paper the server is built as a console application. Regardless of the implementation the core steps are the same.

 

The server is required to house the remote object or objects since a remote server can create several remote objects.  The server first initializes the Object Request Broker.  Next it gets a reference to a basic object adapter (BOA) from the ORB. The server then binds one or more implemented objects to the BOA before telling the BOA that it is ready to run.  Once the BOA recieves a run command it continues listen and process incoming messages until it is told to stop or the process stop.

 

The BOA is responsible for several key items.  The first is registering the object with other services that are required to support the object including the naming and object activation services. The most important thing the BOA handles is how client connections and incoming messages are to be handled.  Currently VisiBroker handles shared, unshared, and server-per-method (additional support can be added to transmit messages encrypted over SSL) policies. In a shared server policy a server can handle multiple clients. Each client can see the exact same object.  The unshared server starts and ends a server for each client. Each client will have its own instance of the remote object. In a server-per-method policy a server is started and ends for each method called. For more information about the BOA see the Visibroker Documentation.

 

The steps to build a server:

  1. Initialize the ORB by using the class method ORB_init to return a pointer to the ORB.
  2. Next use the ORB pointer and method BOA_init to get a pointer to the BOA.
  3. Create an instance of the object including passing a name to bind the object to or no name for transient objects.
  4. Bind the implementation object to the BOA using the method obj_is_ready. If the object has a name the BOA will notify the naming services of the name and reference to the object.
  5. Repeat the above two steps for every remote object that is to be housed in the server.
  6. Set the BOA to listen and handle incoming client requests using the method impl_is_ready.

 

Listing of the server Main function
#include "RemoteCustomer.h"
int main(int argc, char **argv) {
  try {
  // Initialize the ORB and BOA.
    CORBA::ORB_ptr orb = CORBA::ORB_init(argc, argv);
    CORBA::BOA_ptr boa = orb->BOA_init(argc, argv);
  // Create a new account object.
    Customer_ptr customer = new CustomerImpl("customer object");
  // Export the newly created object.
    boa->obj_is_ready(customer);
  // Wait for incoming requests
    boa->impl_is_ready();
  }
  catch(const CORBA::Exception& e) {
    cerr << e << endl;
  }
}

 

To create the server use File|New Console application and include the VCL.  You now will have three projects in your project group.

 

410F.GIF (6445 bytes)

 

In order to compile the program you need to add two directives:

USELIB("c:visigenicvbrokerliborb.lib");
USELIB("customerObject.lib");

The two directives tell the compiler where to find the library code for the ORB and the CustomerObject library that was created.  The orb.lib contains the references into the orb.dll which must be present to run the application. Orb.dll contains the communication code needed by the ORB.

 

**  NOTE that when debugging a CORBA application turn off C++ Exceptions since you will get Exceptions in the CORBA code itself.  This is normal since the ORB code uses exceptions to trigger various functions. **

 

Building a Client

The last step construction step is building the client. In order to access the remote object instead of creating the remote object use the ORB to bind to the remote object and get a stub object with a reference to the real remote object. Once this is accomplished, use the object as though it was local.

 

The client finds an ORB by listening for a broadcast from the Naming server. Once it finds a Naming server it uses that server to find remote objects and bind to them by name or to get a list of available objects.  It then binds to an object.  The binding process returns a stub object and reference to an object.  Once the client has this reference it can use the methods of the object as though the method was local.  The client is shield from the fact it is talking to a remote object because of the stub class.

 

Listing of critical code for binding to the remote object
Customer_var customer;
try { 
// Initialize the ORB. 
  CORBA::ORB_var orb = CORBA::ORB_init(argc,argv); 
// Bind to the remote object
  customer = Customer::_bind(); 
} 
catch(const CORBA::Exception& e) { 
  ShowMessage("CORBA Error Caught!"); 
}

To build the client create a new application.  Arrange GUI controls on the form as shown below:

 

410G.GIF (5407 bytes)

 

Next you will need to code the four buttons and stick two variables into the form class. The orb and customer variables need to be put into the form class as private variables. That way each instance of the form will have its own connection to the remote object and ORB.  The connect button contains the code to bind to a particular object.   In addition it automatically calls the refresh event to display the information in the remote object.  The next and prior buttons call the nextRec and priorRec operations of the remote object.  Notice that once the object is bound you do not need to do anything different to access operations and attributes.

 

Listing of button events
void __fastcall TFrmClient::btnConnectClick(TObject *Sender)
{
  int argc = 1;
  char * const argv[3] = {"CustomerClient.exe","",""};
  orb = CORBA::ORB_init(argc,argv);
  customer = Customer::_bind();
  btnRefreshClick(Sender);
}

void __fastcall TFrmClient::btnNextClick(TObject *Sender)
{
  customer->nextRec();
  btnRefreshClick(Sender);
}

void __fastcall TFrmClient::btnPriorClick(TObject *Sender)
{
  customer->priorRec();
  btnRefreshClick(Sender);
}

void __fastcall TFrmClient::btnRefreshClick(TObject *Sender)
{
  edtName->Text = customer->name();
  edtAddress->Text = customer->address();
  edtCity->Text = customer->city();
  edtState->Text = customer->state();
  edtZip->Text = customer->zip();
}

Before you compile the client do not forget to set the include and lib directories.  Also the include the uselib command for the orb.lib. You do not need to include the uselib for customerObject.lib since the client does not need or care about how the remote object is actually implemented.

 

Smart Agent

Before running the client or the server an instance of Smart Agent must be running on the network. Smart Agent is started by running OSAgent.exe.  Smart Agent provides distributed directory services for the ORB.  It is used by both clients and servers. While you can run an ORB without Smart Agent it provides services that make a CORBA based application more robust.

 

When a server binds an object to a particular name the name, location and reference to the object is maintained by Smart Agent.  If the Smart Agent crashes and another Smart Agent is available the remote objects will automatically fail over to the other instance of Smart Agent.  The fail over mechanism requires no additional code and simple rebinds the remote object and name to the directory of the new Smart Agent.

 

A client queries the Smart Agent for a particular object and receives the location and reference in order to create the stub to talk to the remote object.  In addition, it can find objects not in its own directory by querying other Smart Agents for the object on the behalf of client. A client can also "walk" the list of existing objects by querying the Smart Agent for a list of available objects. If the Smart Agent a client is using becomes unavailable it will automatically look for a new Smart Agent to interact.

 

Run the server

Run the server either using Start|Run Application or using C++Builder Run command.   The server will get an instance to the ORB and BOA, create an instance of the object, bind it to the directory services and the set and wait for client connections.

 

You can verify the server has registered to remote object by using osfind. This utility finds all instances of OSAgent running on the network and what objects are registered with the Smart Agent.  In addition it lists object activations deamons running and object repositories.  If you run a second copy of the object it will show up in the list with the same name.  This is useful because the Smart Agent will automatically split clients between the two instances of the remote object.  This allows you to have load balancing without additional code in your server.

 

Run the client

Run the client either using Start|Run Application or using C++Builder Run command.   The client will get an instance to the ORB and then use the directory services of Smart Agent to locate an object it wants to use.  After binding to the remote object it interacts with the object as though it was local.

 

If you start another instance of the client the two clients will have the same information displayed.  Also notice that the next and prior buttons on either client will cause the record inside the remote object to be moved. Although you currently have to manually refresh the client. 

 

Object Activation Daemon

If you stop the instance of the server or Smart Agent and try to use the client you will get an error.  If you restart the CustomerServer the client automatically reconnects to the remote object. In fact the object activation daemon can be used to help keep a system up and running without manual intervention.

 

The object activation daemon is a service that provides automatic startup for an remote object.  When a client requests a remote object that is unavailable the Smart Agent queries the OAD to see if the object has been setup in the it.  If it has it asks the OAD to start the object, gets a reference to the object and then returns the reference to the client.

 

To use the OAD you must first make sure that an admin and implementation directory exist.  Secondly, you must let Visibroker know where the administration directory is by using the  must be registered in the registery.  You can use the Visibroker Registery Key Wizard to accomplish this.  The Registry Wizard can also be used to change the default port used by the ORB.

  1. Create a directory a base directory for the administration directory.  For example c:visigenicvbrokeradm
  2. Create a directory for the implementation directory which is used by the OAD.   Typically this directory is impl_dir underneth the base directory.  For example c:visigenicvbrokeradmimpl_dir
  3. Start the Visibroker Registry Wizard Tool (VRegEdit.exe) and set the base administration directory

 

410I.GIF (5673 bytes)

 

Once the registry and directory structure is setup you can run the OAD by either selecting it off the start menu or using OAD.exe.  This starts the OAD which is contains a remote object.

 

The last part is registering the objects that you wish to have started automatically. This is accomplished using the oadutil tool.  This utility registers, unregisters and lists objects that are registered with the OAD.

 

To register an object use oadutil reg command:

oadutil reg -i customer -o "customer object" -cpp customerserver.exe

 

The -i command specifies the name of the interface in this case it is customer.   The -o specifies a specific object name to associate with a specific server.   This allows the OAD to start an interface with a particular implementation and object name.  Last is -cpp which is where the name of the executable is placed.   This must either be fully qualified or be in the path.  In addition you can specify an activation policy (shared, unshared, server-per-method), arguments and environment options.  Once the object is register you do not need to start the server the OAD will start it automatically.  Also if the OAD goes down you do not need to reregister the object either since the OAD maintains a persistent list of objects that it is responsible for.

 

To list out objects that are registered with the OAD use:

oadutil list

 

This will give you a list of objects and servers that are registered with the OAD.

 

Example list from oadutil list

 

C:WINDOWS>oadutil list
oadutil list: located 1 record(s)

Implementation #1:
-------------------
repository_id = IDL:Customer:1.0
object_name = customer object
reference data =
path_name = c:progra~1borlandcbuilder3projectscorbacustomercust
omerserver.exe
activation_policy = SHARED_SERVER
args = NONE
env = NONE




C:WINDOWS>

For more information about the OAD see the Visigenic documentation.

 

Interface Repository

The Interface Repository is a database that contains information about objects.   The information includes modules, interfaces, operations and attributes.  The repository can be used for a variety of things including a centralized location for information about interfaces available to programmers and to create clients that find and use interfaces at runtime and without prior knowledge of the interface.  Essentially the repository contains the information found in the idl.

 

To start an instance of the Interface Repository you use the irep.exe.  This utility plays a dual role in that it starts both the repository and can be a client to a repository.  To start a repository you just need to give it a name and file to store and load data.

irep MyRepository c:visigenicvbrokeradmirep_dirmyrep

 

This starts and instance of the repository if it is not already started and a client for browsing the repository.  The repository can display information about interfaces that it has stored in its database. 

 

To load an interface into the repository use idl2ir to compile and load the repository.

idl2ir customer.idl

 

Now you can display the interface by enter the name of an interface and clicking the lookup button.

 

410J.GIF (12708 bytes)

 

The repository is an object you can access from your own clients and invoke methods to find interfaces and operations that a client may wish to perform.  Be aware that there is no connection between the repository and what actually has been implemented.   Also no security is enabled on the repository so any client that can see the repository can add any information that it wishes.

 

For more information about the Repository see the Visigenic Documentation

 

Summary

C++ and CORBA can be used to build distributed object applications. The distributed objects can run on a multitude of platforms and written in many different languages. Using remote objects you can scale take an application to the next level. CORBA has several services including Smart Agent and OAD that allow you to build mission critical applications.

 

Reference

The list of books for more information about CORBA

Inprise Documentation for VisiBroker
Client/Server Survival Guide by Robert Orfalia, Dab Harkey and Jeri Edwards
The Essential Distributed Objects Survival Guide by Robert Orfalia, Dab Harkey and Jeri Edwards
Object-Oriented Technology: A Manager’s Guide by David A. Taylor

 

The following are web site URLs that pertain to the information contained in the courseware.

www.omg.org                                     - Web site for CORBA
www.borland.com/visibroker            - Web site for Inprise's ORB
www.apres-group.com/resources  - This web site contains links to ORB related sites


Server Response from: ETNASC04