![]() |
![]() |
![]() |
![]() |
Orbix Programmer's Guide C++ Edition |
This chapter describes the basic programming steps required to create Orbix objects, write server programs that expose those objects, and write client programs that access those objects.
This chapter illustrates the programming steps using an example named BankSimple. In this example, an Orbix server program implements two types of objects: a single object implementing the Bank interface, and multiple objects implementing the Account interface. A client program uses these clearly-defined object interfaces to create and find accounts, and to deposit and withdraw money.
On Windows and UNIX, the source code for the example described in this chapter is available in the demos\banksimple directory of your Orbix installation. On OS/390, the location of the source code is documented in orbixhlq.DEMOS.README(BANKSIMP), where orbixhlq represents your installation's high-level qualifier. This source code may differ slightly from the code published in this guide. On OpenVMS, the source code for the example is available in the orbix_root:[demos.banksimple] directory.
To develop an Orbix application, you must perform the following steps:
Defining IDL interfaces to your objects is the most important step in developing an Orbix application. These interfaces define how clients access objects regardless of the location of those objects on the network.
An interface definition contains attributes and operations. Attributes allow clients to get and set values on the object. Operations are functions that clients can call on an object.
For example, the following IDL from the BankSimple example defines two interfaces for objects representing a bank application. The interfaces are defined inside an IDL module to prevent clashes with similarly-named interfaces defined in subsequent examples.
The interfaces to the BankSimple example are defined in IDL as follows:
// IDL // In file banksimple.idl
Account create_account (in string name); Account find_account (in string name); };
readonly attribute string name; readonly attribute CashAmount balance;
void withdraw (in CashAmount amount); }; };
This code is explained as follows:
Account interface. It allows you to refer to Account in the Bank interface, before actually defining Account.
Bank interface contains two operations: create_account() and find_account(), allowing a client to create and search for an account.
Account interface contains two attributes: name and balance; both are readonly. This means that clients can get the balance or name, but cannot directly set them. If the readonly keyword is omitted, clients can also set these values.
Account interface also contains two operations: deposit() and withdraw(). The deposit() operation allows a client to deposit money in the account. The withdraw() operation allows a client to withdraw money from the account.
The parameters to these operations are labelled with the IDL keyword in. This means that their values are passed from the client to the object. Operation parameters can be labelled as in, out (passed from the object to the client) or inout (passed in both directions).
You must compile IDL definitions using the Orbix IDL compiler. Before running the IDL compiler, ensure that your configuration is correct.
You should ensure that the environment variable IT_CONFIG_PATH is set to the location of iona.cfg, the root Orbix configuration file.
On UNIX, if iona.cfg is in directory /local/iona, perform the following steps:
sh enter:
% IT_CONFIG_PATH=/local/iona % export IT_CONFIG_PATH% setenv IT_CONFIG_PATH /local/iona
LD_LIBRARY_PATH to include the location of the Orbix lib directory in a similar manner.
On Windows, if iona.config is in directory C:\iona\config, enter the following at the DOS prompt:
set IT CONFIG PATH = C:\iona\config
On OS/390, you can specify the IT_CONFIG_PATH environment variable using ENVAR in the list of runtime options preceding the arguments to any Language Environment program. For example:
//STEP1 EXEC PGM=progname.
// PARM='ENVAR("IT_CONFIG_PATH=TEST.PARMS(ORBIXCFG)")
// /arguments'
Refer to the Orbix for OS/390 Administrator's Guide for full details about running Orbix on OS/390.
On OpenVMS, the IT_CONFIG_PATH is a system logical, which is set to orbix.root: [config.your_host]. You can override this, for example:
$define IT_CONFIG_PATH orbix_root: [config.some_other_host].
The IDL compiler checks the validity of the specification and generates C++ code that allows you to write client and server programs.
To compile the Bank and Account interfaces defined in file banksimple.idl, run the IDL compiler as follows:
idl [options] banksimple.idl
The -B compiler option produces BOAImpl classes for the server. Refer to Appendix A, "Orbix IDL Compiler Options" for a complete list of IDL compiler options.
On OS/390, you can run the IDL compiler in batch or as a TSO command. All the JCL procedures that are supplied by IONA are stored in orbixhlq.PROCS. The JCL to run the IDL compiler in batch is as follows:
//STEP1 EXEC PROC=ORXI, // INTERFACE=BANKSIMP, // IDLPARMS='-B', // IDL=orbixhlq.DEMOS.IDL, // HH=output.pds.hh, // STUBS=output.pds.stubs
The TSO command to run the IDL compiler on OS/390 is as follows:
CALL 'orbixhlq.LOAD(IDL)' '-Borbixhlq.DEMOS.IDL(BANKSIMP)' ASIS
You must pass a fully-qualified data set name as an argument to the IDL compiler. The IDL compiler reads the input from this PDS and then writes the generated C++ files to it.
On OpenVMS, the syntax for the complier command is:
The IDL compiler produces three C++ files that communicate with Orbix:
These source files contain C++ definitions that correspond to your IDL definitions. These C++ definitions allow you to write C++ client and server programs.
By default, these files are named as follows:
The files banksimple.hh and banksimple.client.cxx define the C++ code that a client uses to access a Bank object. This code is termed the client stub code. For example, the banksimple.hh file for the BankSimple IDL includes a class to represent Bank and Account objects from a client's point of view.
The IDL declarations for the Account interface include the C++ definitions in the following code extract:
// C++
// In file banksimple.hh
// Automatically generated by the IDL compiler.
class Account: public virtual CORBA::Object {
public:
// CORBA support functions and error handling are
// omitted here for clarity
virtual char* name ()
throw (CORBA::SystemException);
virtual CashAmount balance ()
throw (CORBA::SystemException);
virtual void deposit (CashAmount amount)
throw (CORBA::SystemException);
virtual void withdraw (CashAmount amount)
throw (CORBA::SystemException);
};
The environment argument (the last argument passed to each method) is omitted here.
This class represents the IDL Account interface in C++ allowing C++ clients to treat Account objects like any other C++ object. The readonly name and balance attributes map to member functions of the same name. The deposit() and withdraw() operations map to C++ member functions with equivalent parameters.
The files banksimple.hh and banksimple.server.cxx define the C++ code that allows a server program to implement IDL interfaces and accept operation calls from clients to objects. This code is known as the object skeleton code. These server-side skeletons receive CORBA calls and pass them onto application code. When implementing a server using the BOAImpl approach, you inherit from a BOAImpl class generated by the IDL compiler.
For the Account interface the BOAImpl class includes the following C++ definitions:
// C++
// In file banksimple.hh
// Automatically generated by IDL compiler.
class AccountBOAImpl: public virtual Account {
public:
virtual char* name ()
throw (CORBA::SystemException) = 0;
virtual CashAmount balance ()
throw (CORBA::SystemException) = 0;
virtual void deposit (CashAmount amount)
throw (CORBA::SystemException) = 0;
virtual void withdraw(CashAmount amount)
throw (CORBA::SystemException) = 0;
};
To implement the Account interface, you must inherit from this class and override the pure virtual functions that represent IDL operations with application code.
This example uses the CORBA BOAImpl approach to implementing an IDL interface. It uses two classes to implement the Bank and Account IDL interfaces in C++: BankSimple_BankImpl and BankSimple_AccountImpl. These classes inherit the IDL compiler-generated BankSimple::BankBOAImpl and BankSimple::AccountBOAImpl classes. These base classes provide all the Orbix functionality. All that remains is to override the abstract member functions that represent the IDL operations.
For example, the code for BankSimple_BankImpl is as follows:
// C++ // In file BankSimple\banksimple_bankimpl.h // Implementation class for the Bank IDL interface. ...
create_account(const char* name, CORBA::Environment&); virtual BankSimple::Account_ptr find_account( const char* name, CORBA::Environment&); // C++ constructor and destructor.
virtual ~BankSimple_BankImpl(); protected: static const int MAX_ACCOUNTS;
};
This code is explained as follows:
Account type is represented by an Account_ptr.
Account_var. These are like pointers; for more information on Account_var, refer to "CORBA Object References" on page 40.
You can implement the member functions of BankSimple_BankImpl as follows:
// C++ // In file banksimple_bankimpl.cxx #include "banksimple_bankimpl.h" #include "banksimple_accountimpl.h"
- const int BankSimple_BankImpl::MAX_ACCOUNTS = 1000;
BankSimple_BankImpl::BankSimple_BankImpl() : m_accounts(new BankSimple::Account_var[MAX_ACCOUNTS]) { // Make sure all accounts are nil. for (int i = 0; i < MAX_ACCOUNTS; ++i){ m_accounts[i] = BankSimple::Account::_nil(); } } BankSimple_BankImpl::~BankSimple_BankImpl() { delete [] m_accounts; } // Add a new account. BankSimple::Account_ptr BankSimple_BankImpl::create_account (const char* name, CORBA::Environment& ) { int i = 0; for ( ; i < MAX_ACCOUNTS && !CORBA::is_nil(m_accounts[i]); ++i) {} if (i < MAX_ACCOUNTS){
cout << "create_account: Created account with name: " << name << endl;
} else{ cout << "create_account: failed, no space left!" << endl;
} } // Find a named account. BankSimple::Account_ptr BankSimple_BankImpl::find_account (const char* name, CORBA::Environment& ) { int i = 0; for ( ; i < MAX_ACCOUNTS &&( CORBA::is_nil(m_accounts[i]) || strcmp(name, m_accounts[i]->name()) != 0); ++i) { } if (i < MAX_ACCOUNTS){ cout << "find_account: found account named" << name << endl; return BankSimple::Account::_duplicate(m_accounts[i]); } else{ cout << "find_account: no account named" << name << endl; return BankSimple::Account::_nil(); } }
The code is explained as follows:
1000.
Account reference is returned from create_account() and find_account() operations, it must be duplicated. According to CORBA memory management rules, this reference is released by the caller.
Refer to the banksimple\demos directory of your Orbix installation for the corresponding code for BankSimple_AccountImpl.
To write a C++ program that acts as an Orbix server, perform the following steps:
This section describes each of these programming steps in turn.
Because Orbix uses the standard OMG IDL to C++ mapping, all servers and clients must call CORBA::ORB_init() to initialize the ORB. This returns a reference to the ORB object. The ORB methods defined by the standard can then be invoked on this instance.
// C++
// In file server.cxx
...
try {
...
// Initialize the ORB.
CORBA::ORB_var orb = CORBA::ORB_init(argc,argv,"Orbix");
...
}
catch (const CORBA::SystemException& e) {
cout << "Unexpected exception" << e << endl;
}
In this code sample, the argc parameter refers to the number of arguments in argv. The argv parameter is a sequence of configuration strings used if "Orbix" is a null string; the string "Orbix" identifies the ORB. Refer to the Orbix Reference Guide for more information on CORBA::ORB_init().
Orbix raises a C++ exception to indicate that a function call has failed. All CORBA exceptions derive from CORBA::Exception. Many Orbix functions (for example, ORB_init()) and all IDL operations may raise a CORBA system exception, of type CORBA::SystemException.
You must use C++ try/catch statements to handle exceptions, as illustrated in the preceding code sample. In the remainder of this chapter, try/catch statements are omitted for clarity.
To create an implementation object, you must create an instance of your implementation class in your server program. Typically a server program creates a small number of objects in its main() function, and these objects may in turn create further objects. In the BankSimple example, the server creates a single bank object in its main() function. This bank object then creates accounts when create_account() is called by the client.
For example, to create an instance of BankSimple::Bank in your server main() function, do the following:
// C++
// In file server.cxx
#include "banksimple_bankimpl.h"
int main ( ... ) {
...
// Create a bank implementation object.
BankSimple::Bank_var my_bank = new BankSimple_BankImpl;
...
}
A server program can create any number of implementation objects for any number of IDL interfaces.
Note that implementation object has a name that uniquely identifies it to the server. This name is called the "marker" (discussed more in `Making Objects Available in Orbix" on page 159). The above code does not explicitly set the marker for the Bank implementation object, hence the ORB picks an unused random name. In general, you always need to explicitly set the marker from your implementation objects (see `Making Objects Available in Orbix" on page 159).
When a server instantiates an Orbix object (for example, one inheriting from the BOAImpl class), it is automatically registered with Orbix as a distributed object. To make objects available to clients, the server must call the Orbix function CORBA::BOA::impl_is_ready() to complete its initialization and to process operation calls from clients.
You can code a complete server main() function as follows:
// C++
// In file server.cxx
#include "banksimple_bankImpl.h"
#include "banksimple_accountImpl.h"
#include <it_demo_nsw.h>
// Server mainline.
int main (int argc, char* argv[]) {
try {
// Use standard demo server options.
CORBA::BOA_var boa = orb->BOA_init(argc, argv, "Orbix_BOA"); // Set diagnostics. orb->setDiagnostics(serveropt.diagnostics()); // Set server name.
// are connected. boa->setNoHangup(1); // Set up Naming Service Wrappers (NSW).
... // Create a bank implementation object.
if (serveropt.bindns()) { cout << "Binding objects in the Naming Service" << endl; ns_wrapper.registerObject(bank_name, my_bank); } // Server has completed initialization, wait for // incoming requests.
serveropt.timeout()); // impl_is_ready() returns only when Orbix times-out // an idle server. cout << "server exiting" << endl; } catch (const CORBA::Exception& e) { cerr << "Unexpected exception" << e << endl; return 1; } return 0; };
This code is explained as follows:
demos\demolib directory contains the standard server and client options used by the Bank series examples in this book.
The ORB and the BOA are different views of the same ORB API--this object is also available via the global variable CORBA::Orbix. However, use of this variable is not CORBA-defined and is discouraged.
setServerName(serveropt.server_name()). This is required by Orbix before exporting object references.
BankSimple.Bank is the name that the bank object is known by in the Naming Service.
BankSimple instance is my_bank. This object implements an instance of the IDL interface Bank. This is called directly from client applications using the CORBA standard Internet Inter-ORB Protocol (IIOP).
registerObject().
CORBA::BOA::impl_is_ready() operation is called to complete server initialization. This takes a server name and a timeout value as parameters. You can specify any name for your server; however, the name should match the name used to register the server in the Implementation Repository, and the argument used to call setServerName().
The timeout value indicates the period of time, in milliseconds, that the impl_is_ready() call should block for while waiting for an operation call to arrive from a client. If no call arrives in this period, impl_is_ready() returns. If a call arrives, Orbix calls the appropriate member function on the implementation object and the timeout counter starts again from zero.
To write a C++ client program to an Orbix object, you must perform the following steps:
This section describes each of these steps in turn.
All clients and servers must call CORBA::ORB_init() to initialize the ORB. This returns a reference to the ORB object. The ORB methods defined by the standard can then be invoked on this instance.
A CORBA object reference identifies an object in your system. When an object reference enters a client address space, Orbix creates a proxy object that acts as a local representative for the remote implementation object. Orbix forwards operation invocations on the proxy object to corresponding functions in the implementation object.
Consider an object reference as a pointer that can point to an object in a remote server process. Object references to an object of interface X are represented by a type X_ptr, which behaves like a normal C++ pointer.
An object reference requires some memory in the client (the memory needed by the proxy object), so you must release each reference when finished by calling CORBA::release(). The CORBA::release() method releases the client memory used by the object reference--it does not affect the remote server object.
For interface X, the IDL compiler also generates a smart pointer class called X_var that automates memory management. X_var behaves just like X_ptr, except it releases the reference when it goes out of scope, or if a new reference is assigned.
The flexible CORBA-defined way to obtain object references is to use the standard CORBA Naming Service. The CORBA Naming Service allows a name to be bound to an object and allows that object to be found subsequently by resolving that name within the Naming Service.
A server that holds an object reference can register it with the Naming Service, giving it a name that can be used by other components of the system to find the object. The Naming Service maintains a database of bindings between names and object references. A binding is an association between a name and an object reference. Clients can call the Naming Service to resolve a name, and this returns the object reference bound to that name. The Naming Service provides operations to resolve a name, to create new bindings, to delete existing bindings, and to list the bound names.
A name is always resolved within a given naming context. The naming context objects in the system are organized into a graph, which may form a naming hierarchy, much like that of a file system. The following sample code shows how the client uses the Naming Service wrapper functions to obtain an object reference:
// C++ // In file client.cxx ... // Naming Service Setup. // Create a Naming Service Wrapper object.
IT_Demo_NSW ns_wrapper;
// Get a reference to the required object from the NSW.
// Narrow the object reference.
if (CORBA::is_nil(bank)) { cerr << "Object \"" << object_name
<< "\"in the Naming Service" << endl
<< "\tis not of the expected type."<< endl; return 1; } // Start client menu loop
main_menu.start(); } ... }
This code is described as follows:
BankSimple.Bank is the name by which the bank object is known in the Naming Service.
nswrapper::resolveName() retrieves the object reference from the Naming Service placed there by servers. The object_name parameter is the name of the object to resolve. This must match the name used by the server when it calls registerObject().
resolveName() is of type CORBA::Object. You must call _narrow() to safely cast down from the base class to the Bank IDL class, before you can make invocations on remote Bank objects. The client stub code generated for every IDL class contains the _narrow() function definition for that class.
Bank clients. This menu enables you to find or create accounts by calling the appropriate C++ member function on the object reference.
To access an attribute or an operation associated with an object, call the appropriate C++ member function on the object reference. The client-side proxy redirects this C++ call across the network to the appropriate member function of the implementation object.
The main BankSimple client program calls a simple interactive menu. This enables you to call IDL operations on a Bank. The following code extracts show the code called when you choose to create or find an account:
// C++
// In file bankmenu.cxx
void BankMenu::do_create() throw(CORBA::SystemException) {
cout << "Enter account name: " << flush;
CORBA::String_var name = IT_Demo_Menu::get_string();
- BankSimple::Account_var account = m_bank->create_account(name);
// Start a sub-menu with the returned account reference.
AccountMenu sub_menu(account);
sub_menu.start();
}
// do_find -- calls find account and runs account menu.
void BankMenu::do_find throw (CORBA::SystemException) {
cout << "Enter account name: " << flush;
BankSimple::Account_var account = m_bank->find_account(name); AccountMenu sub_menu(account) sub_menu.start(); }
This code is explained as follows:
m_bank is a Bank_var--a C++ helper class automatically generated by the IDL compiler from the Bank interface. This is used like a normal C++ pointer to call IDL operations just like C++ operations.
The String_var name variable is used for the account name entered. The caller is not responsible for releasing the memory--String_var automatically does this when it goes out of scope.
Use the C++ arrow operator (->) to access the operations defined in IDL through a BankSimple::Bank_var object. Call those member functions using normal C++ calls and test for errors using C++ exception handling.
To build the client and server, you must compile and link the relevant C++ files with the Orbix library. On UNIX, this is liborbix; on Windows, this is ITMi.lib. On OpenVMS, this is liborbix.olb. These files are available in the Orbix lib directory.
Note: For demonstration-specific functionality, you must also includelibdemo.aon UNIX anddemolib.libon Windows.
To build the client application, compile and link the following C++ files, and the Orbix library:
client.cxx is the source file for the client main() function.
To build the server application, compile and link the following C++ files, and the Orbix library.
server.cxx is the source file for the server main() function.
The Orbix demos/banksimple directory includes a makefile that compiles and links the bank client and server demonstration code.
To build the executables, type one of the following in the demos\banksimple directory of your Orbix installation:
| Windows |
>nmake |
| UNIX |
%make |
| OpenVMS | $mms |
On OS/390, JCL is provided to build and run the demo in ORBIXhlq.DEMOS.BUILD.JCL.
To run the application, do the following:
orbixd) on the server host.
Before a client can access a server, the server must be registered with the Orbix daemon. Before running the Orbix daemon, ensure that the environment variable IT_CONFIG_PATH is set as described in "Setting Up Configuration for the IDL Compiler" on page 28.
You can run the Orbix daemon on the server host by typing orbixd at the command line or using the Start menu on Windows.
On OS/390, the daemon can be run as a batch job or a started task. Sample JCL is supplied in orbixhlq.JCL(ORBIXD).
On OpenVMS, the daemon should be started using the command:
The Implementation Repository is the component of Orbix that stores information about servers available in the system. Before running your application, you must register your server in the Implementation Repository.
To register the server(s), use either the Server Manager GUI tool or run the Orbix putit command on the server host as follows:
putit server_name server_executable
To register the server(s), you can execute utilities either by TSO call commands or the Orbix ISPF panels. For example:
CALL orbixhlq.LOAD(PUTIT)'server_name execution_jcl_location' ASIS
On all platforms, server_name is the name of your server passed to impl_is_ready().
If a server binds names in the Naming Service, you may need to run it once to allow it to set up the name bindings. Details of how to do this depend on the server used. The demonstrations provide a makefile that do the necessary server registration and set up names in the Naming Service.
To register the server, type one of the following:
| Windows |
> nmake register |
| UNIX |
% make register |
| OpenVMS | $mms register |
On OS/390, the details for each demonstration are documented in a member of orbixhlq.DEMOS.README.
When a client binds to an object in a server registered in the Implementation Repository, the Orbix daemon automatically launches the server executable file. Consequently, you can run the client without running the server in advance.
Before running the client, ensure that the environment variable IT_CONFIG_PATH is set as described in "Setting Up Configuration for the IDL Compiler" on page 28.
Run the example client by entering client at the command-line prompt. The client displays a text menu allowing you to choose the actions you want to take, and then prompts you for the necessary information. The server outputs messages when it processes incoming calls. You can see these messages by looking at the application shell window launched by the Orbix daemon.
Run the example client using the following TSO command:
CALLorbixhlq.DEMOS.LOAD(BANKCLNT)'ENVAR("IT_CONFIG_PATH=orbixhlq.PROCS(ORBIXCFG)")/'
The client displays a text menu allowing you to choose the actions you want to take, and then prompts you for the necessary information. The server outputs messages when it processes incoming calls. You can view these messages by looking at the SYSPRINT output.
On OpenVMS, run the example client by enteringmcr []client.exeat the command line prompt. The client displays a text menu that allows you to choose the actions you want to take and then prompts you for the necessary information. The server outputs messages when it processes incoming calls. These messages are logged to theorbix$log:process_name.logfile. You can identify the name of the process your server is running by typingobservat the command line prompt.
To develop a distributed application with Orbix, do the following:
|
support@iona.com Copyright © 2000, IONA Technologies PLC. |