Programming in C++

The C++ langauge is an object oriented extension to 'C' and is arguably, the most natural 'fit' for CORBA as IDL was modelled on C++. Consequently, many of the features such as exceptions and modules which are imperfectly modelled in 'C' fit more easily into the C++ programming model. Examples from the banking application are used in the text as explanation.

Initializing The ORB

The first thing to be done for both client and server is to initialize the ORB so that calls to the ORB can be made. This is done by the following code segment

int main( int argc, char ** argv )
{
    try
    {
        CORBA::ORB orb = CORBA::ORB_init( argc, argv, "c++orb" ) ;
        ...
        ...
    }
    catch ( CORBA::Exception & e ) 
    {
        cerr << e << endl ;
    } 
}

This call to the ORB will process and remove any arguments which start with -ORB. There are no such parameters currently defined in Engine Room CORBA. The final parameter in this call is optional. You may notice the try-catch block around the call. In general, all calls to CORBA objects whether pseudo objects such as the ORB and BOA objects or user defined objects can throw exceptions of type CORBA::Exception

Initializing The BOA

The purpose of the Basic Object Adaptor is to process requests to server side objects which are attached to it. This only needs to be initialized for the server side and is done by the following code segment :-



int main( int argc, char ** argv )
{
    try
    {
        CORBA::ORB orb = CORBA::ORB_init( argc, argv, "c++orb" ) ;
        CORBA::BOA_ptr boa = orb->BOA_init( argc, argv ) ;    
        ...
        ...
    }
    catch ( CORBA::Exception & e ) 
    {
        cerr << e << endl ;
    } 
}

This call will process and remove any arguments which start with -BOA. The following are defined :-

 
-BOAconns=num Sets the number of connections supported by the BOA to num
-BOAtimeout=num

Sets the timeout to num. After this timeout, the function CORBA_BOA_implementation_is_ready will return with error code CORBA_TIMEOUT

-BOAport=num Sets the port on which the BOA listens for new connections.

 

Handling IOR's in C++

IOR's are processed by the following member functions of the CORBA::ORB class :-

        char * object_to_string( CORBA::Object_ptr obj ) ;

This function produces an IOR from an object reference. This call is normally used on the server side to publish the object to a well known location or server but may be used on the client side as well. The IOR returned is a CORBA string and should be released with the delete operator. The following code illustrates its use :-

        char * ior = orb->object_to_string( obj ) ;

And

    CORBA::Object_ptr string_to_object( char * ior ) ; 

This produces an object reference from a string. This call is normally used on the client side to gain an object reference. The object returned should be released with the delete operator. The following code illustrates its use :-

        CORBA::Object_ptr p = orb->string_to_object( ior ) ;

String Functions

CORBA C++ programming manages its own string types. Strings should only be manipulated by these functions otherwise memory corruptions and leaks may occur.

Strings are allocated with :-

    char * CORBA::string_alloc( ULong len ) ;

    inline NS_STATIC void string_free( char * str )
    { 
        if ( str )
            CORBA_free( str ) ; 
    }

and wide strings are allocated with :-

    char * CORBA::wstring_alloc( ULong len ) ;

These functions return a (wide) string capable of holding len characters

Strings are copied with :-

    char * CORBA::string_dup( const char * str ) ;

and wide strings are copied with :-

    char * CORBA::wstring_dup( const CORBA::Wchar * str ) ;

The length of a string is found, as usual, with the standard library function strlen. However, the length of a wide string is obtained with :-

    int CORBA::wstring_len( const CORBA::Wchar * src ) ;

Strings should be released with

    void CORBA::string_free( const char * src ) ;

and

    void CORBA::wstring_free( const CORBA::Wchar * src ) ;

Managing Memory

All pointers allocated by CORBA C++ calls, including ORBs,BOAs, CORBA objects, sequences and "any" parameters should be freed with the delete operator with the exception of strings which have their own deletion functions.

In addition, any distinct type T defined in the source IDL will have a new operator associated with it to allocate memory for the type. The pointer returned by this function should also be freed using the delete operator.

Var Variables

All types defined in the source IDL, and some pseudo types such as ORB and String have an additional class generated to help with memory management. This class has the same name with _var appended to it. e.g. String_var for strings and Object_var for objects. These _var classes are wrappers for the pointer and may be used anywhere the pointer is. However, they allow automatic (i.e. stack) allocation of the variable and memory allocated to them are freed when the block enclosing them is exited. This removes the need for the programmer to remember to delete them. This is particularly useful when exceptions may be occur within the block as, in that case, the delete statement might not be encountered in the normal program flow which will lead to memory leakages in the application.

For example, for a structure myStr

    void function()
    {
        myStr_var V( new myStr() ) ;
        ...
        ...
        ...
        ...
    }

The storage associated with V is automatically released at the end of the function.

Using Sequences

Sequences are used in CORBA as variable length single dimension arrays. To manage these types in C++ a number of classes are generated to allocate and examine them. Given the following IDL :-

   
    typedef sequence<myStruct,8> mySeq ;

Then the following will be generated for the mySeq type :-

   
    class mySeq
    {

    public:
        static myStruct * allocbuf( CORBA::ULong len ) ;
        mySeq() ; 
        mySeq( CORBA::ULong length, myStruct * data, 
            CORBA::Boolean _release = CORBA::FALSE ) ;
        ...
        ...
        ...
    }
    

The default constructor mySeq() creates an empty sequence with a maximum length of 8. The static member function allocbuf() allocates an array of length len of type myStruct. The other constructor creates a sequence of length length using the data pointed to by data.

If _release is TRUE then data is released when the sequence is deleted, otherwise managment of the memory pointed to by data is the programmer's responsibility.


The following code snippet illustrates sequence usage

{
    const CORBA::ULong len = 4 ;  
    myStruct * str = mySeq->allocbuf( len ) ;
    str[ 0 ] = ... ;
    str[ 3 ] = .... ;

    mySeq seq( len, str, CORBA::TRUE ) ;
        ...
        ...
        ...

    delete seq ;
}

Both seq and str are released by the delete operator. A var type is also generated for sequences.

Type Codes

A function T_type() which returns a type code is generated for each distinct type T can be found. Therefore for a type myType the following will return the type code :-

    CORBA::TypeCode tc = myType_type() ;

Using Any Values

Any values are used to transfer values whose type is not known at the time of interface definition. An any value consists of a value paired with a type code which describes the type of the value. In C++ a class CORBA::Any is defined to handle any values.

{
    CORBA::Any a, * b ;
    a <<= ( CORBA::Short )200 ;
    obj->func( a, b ) ;
    char * str ;
    b >>= str ;
    cout << str << endl ;
    delete b ;
    CORBA::string_free( str ) ;

}

The following function allocates an any value :-

   CORBA::Any v = new CORBA::Any() ;

For each distinct type, an insert and extract operator are generated to facilitate handling any values of that type. Using these operators, ensures that the any values are type safe. For a type myType the following insertion operators are generated.

    void operator<<=( CORBA::Any & any, const myType & v ) ;

    void operator<<=( CORBA::Any & any, myType & v ) ;

The first form copies the value being inserted leaving the value available to the application for further manipulation. The second form consumes the value and it should not be referred to again in the application. The value passed to the second form must be capable of being deleted in the normal manner as the memory managed by this value becomes the any value's responsibility.

The following extraction operator is generated :-

   CORBA::Boolean operator>>=( const CORBA::Any & any, myType *& v ) ;

The extraction operator returns CORBA::TRUE if the extraction was successful and CORBA::FALSE if unsuccessful. Attempting to extract into a type which is not the same as the one contained in the any value will always result in returning a CORBA::FALSE value.

Exceptions

All C++ exceptions inherit from the base class CORBA::Exception which is defined below :-

    class Exception
    {
    public:
        ...
        ...
        char * id() ;
        ReplyStatus major() ; 
    } ;

The member function major() returns the reply status of the exception which is defined below :-

    enum ReplyStatus 
    {
        NO_EXCEPTION,
        USER_EXCEPTION,
        SYSTEM_EXCEPTION
    } ;

The member function id() returns the exception identifier which is a string representation of the exception name.

 

System Exceptions

For each system exception, a class has been defined in the CORBA namespace to represent it. For example, the system exception BAD_PARAM maps to CORBA::BAD_PARAM class. All system exception classes inherit from the class CORBA::SystemException which is defined below :-

    class SystemException : public Exception
    {
    public:
        SystemException() ;
        SystemException( const SystemException & val ) ;
        SystemException( ULong minor, CompletionStatus status ) ;
        static SystemException * _narrow( Exception * e ) ;
        ULong minor() const ;
        void minor( ULong minor ) ;
        void _raise() ;
        CompletionStatus completed() ;
        void completed( CompletionStatus status ) ;
    } ;

This class has constructors to create an empty system exception, or to initialize a system exception from another one, or to set the minor code and completion status. It also has get and set functions to manipulate the minor code and completion status.

A system exception has a minor code which can be set. This is only of relevance to your own application and can be used to distinguish between different types of the same system exception. In addition, the completion status can be set to indicate, approximately, in which phase of the operation the exception occurred. This is defined by an enumeration and can take the values :-

    enum CompletionStatus { 
        COMPLETED_YES, COMPLETED_NO, COMPLETED_MAYBE } ;

Individual system exception classes follow the pattern of the BAD_PARAM class :-

    class BAD_PARAM : public SystemException
    {
    public:
        BAD_PARAM() ;
        BAD_PARAM( const SystemException & val ) ;
        BAD_PARAM( ULong minor, CompletionStatus status ) ;
        void _raise() { throw * this ; }
    } ;

User Exceptions

All user exceptions inherit from the base class CORBA::UserException which in turn inherits from CORBA::Exception. CORBA::UserException is defined below :-

    class _ER_IMPORT UserException : public Exception
    {
    public:
        UserException( char * id ) ;
        UserException() ;
        UserException( const UserException & e ) ;
        static UserException * _narrow( Exception * e ) ;
        void _raise() ;
    } ;

For example, the following IDL defines a user exception called NotAuthorized :-

    exception NotAuthorized
    {
        string<100> reason ;
        short code ;
    } ;

This will be mapped to:-

    class NotAuthorized : public CORBA::UserException 
    {
    public:
        NotAuthorized() ;
        NotAuthorized( const NotAuthorized & e ) ;
        void _raise() ;
        ...
        ...
        CORBA::String_var reason ;
        CORBA::Short code ;
    } ;

The reason and code fields can be set directly by the programmer.

Raising Exceptions

All exceptions can be raised by calling the _raise() member function or by using the C++ throw operator. For example, the following code will instantiate and raise the BAD_PARAM exception in one line :-

        CORBA::BAD_PARAM( 7, CORBA::COMPLETED_YES )._raise() ;

The following code instantiates and raises the NotAuthorized exception from the banking example :-

        const char * why = "Insufficient funds" ;
        NotAuthorized na ;
        na.reason = why ;
        na.code = 200 ;
        na._raise() ; 

Catching Exceptions

Exceptions are caught in the normal manner in C++ encloding code in try/catch blocks. For example, the following code will catch the BAD_PARAM and NotAuthorized exceptions :-

    try
    {
    ...

	// some code which may result in exceptions
    ...

    }        
    catch ( CORBA::BAD_PARAM & e )
    {
        cout << "System exception BAD_PARAM " << endl ;

        cout << "minor code = " << e.minor() << " completion status = " << 
                e.completed() << endl ;
    }
    catch ( NotAuthorized & na )
    {
        cout << "Exception " << na.id() << " reason = " << ( char * )na.reason << 
            " code = " << na.code << endl ;
    }        

Client Programming in C++

Apart for ORB and object setup, client programming in C++ is no different from using any other C++ object which may throw exceptions. Very few calls are required to set up the remote connection between the server object and the client object. IDL object operations are presented as simple C++ function calls. The sequence is as follows :-

  1. First you have to initialize the ORB

  2. Get an initial object reference

  3. Narrow the object reference

  4. Then invoke operations on the object

  5. Release memory associated with the ORB and Objects

Getting An Initial Object Reference

The initial object reference can be obtained by transforming an IOR to an object reference. This is done with the CORBA::ORB::string_to_object member function. How the IOR is published is application dependent. The following code creates an object from an IOR

    CORBA::ORB orb ;
    char * ior ;
    CORBA::Object_ptr obj ;
    ...
    ...
    ...
    
    obj = orb->string_to_object( ior ) ;

Narrowing An Object Reference

To be able to use an object which is of type of CORBA::Object_ptr it has to be narrowed to a useful instance. For each interface defined in IDL a static member function _narrow is generated. This will return an instance of the object required or raise the sytem exception OBJECT_NOT_EXIST. For an interface Account, the following code creates an object from an IOR

    CORBA::ORB orb ;
    char * ior ;
    CORBA::Object_ptr obj ;
    ...
    ...
    ...
    
    obj = orb->string_to_object( ior ) ;
    Account_ptr = Account::_narrow( obj ) ;
    

 

Invoking Operations

In C++, operations are mapped to class member functions.

The following IDL :-

    interface iface1
    {
        short op1( in short I, inout long IO, out string<20> O ) ;
    } ;

Maps to the following :-

    class iface1
    {
    public:
        CORBA::Short op1( CORBA::Short I, CORBA::Long & IO, char *& O ) ;
    } ;

This is called like any other normal C++ member function. For example :-

    iface1_ptr p ;
    CORBA::Long IO ;
    char * str ;
    ...
    ...
    ...
    p->op1( 45, IO, str ) ;

    CORBA::string_free( str ) ;

If constructed types are used in the function, then you will have to allocate memory for in and inout parameters. Return and out parameters will be allocated by the function you are calling. All memory allocated by the programmer or the function should be freed. Note that the total memory size of inout parameters may have changed after calling the operation if the inout parameter is a string or sequence type. If an exception has been thrown, no memory will have been allocated for return and out parameters and inout parameters will not have been altered.

Server Programming in C++

After implementing the server object the procedure is :-

  1. Initialize the ORB

  2. Initialize the BOA

  3. Create server objects

  4. Process Requests

  5. Release memory associated with the ORB and Objects

Implementing Server Objects

A server class for each interface is generated in the header file. This must be used as the base class for implementing the server. For example, the following IDL

    interface iface1
    {
        short op1( in short I, inout long IO, out string<20> O ) ;
    } ;

Maps to the following in the header file

   class POA_iface1
    {
    public:
        POA_iface1() ;
        virtual CORBA::Short op1( CORBA::Short I, CORBA::Long & IO, char *& O ) 
            throw( CORBA::SystemException ) = 0 ;
   } ;

As all operations are abstract and virtual in the base class, they must be implemented in the server class written by you.

If constructed types are used in the function, the programmer will have to allocate memory for return and out parameters. It may also be necessary to allocate memory for inout parameters especially in the case of sequences and strings if the amount of memory required by the parameter increases.

in parameters are allocated by the calling function. All memory allocated by the programmer or the calling function is released by the calling function.

All operations may raise a system exception but functions may only raise user exceptions, if they have been defined as doing so in the IDL source.

Publishing An Object Reference

The object reference can be published by transforming an object reference to an IOR . This is done with the CORBA::object_to_string function. How the IOR is published is application dependent. For example, it may be written to a file, or shared memory, sent as an applet parameter or sent to a naming server.

The following code creates an IOR from an object :-

    CORBA::ORB orb ;
    char * ior ;
    CORBA::Object_ptr obj ;
    ...
    ...
    ...
    
    ior = orb->object_to_string( obj ) ; 

Creating Server Objects

Server objects are instantiated by calling the new operator of the object or by using an automatic constructor of the class which implements the interface. For example the class BankImpl is created by the programmer to implement the POA_Bank interface :-

class Bank_Impl : public POA_Bank
{
public:
    Bank_Impl() {} 
    ~Bank_Impl() ;
    Account_ptr createAccount(  CORBA::ULong accountNum ) 
        throw( CORBA::SystemException, AccountExists ) ;
    Account_ptr getAccount( CORBA::ULong accountNum ) 
        throw( CORBA::SystemException, NoAccountExists ) ;
} ;

Instantiating the class is as simple as :-

BankImpl bank ;

or

BankImpl * bank = new BankImpl() ; 

Connecting Server Objects To The BOA

This is done by calling the obj_is_ready() member function of the BOA object.

    try
    {
        CORBA::BOA_ptr boa ;
        ...    

        Bank_Impl bank ;

        boa->obj_is_ready( &bank ) ;
        ...    
    }

Processing Requests

After ORB, BOA and object(s) initialization the following call should be issued to start processing requests on the object.

    int r = boa->impl_is_ready() ;

This function only returns in case of error or unhandled exception or if a timeout (default=30 seconds) is set on the BOA.

Copyright © 2000 Engine Room Software Ltd