Chapter 17 Creating CORBA C++ Clients


Writing CORBA C++ clients

These section describes how to code a CORBA C++ client that invokes component methods:

Adding required include and namespace declarations

Stub header files are generated for all IDL modules that include interfaces that the component implements--you must include all these stub header files. In addition to the stub header files, you must also include SessionManager.hpp (which contains the classes and functions that allow a C++ client to create and destroy sessions) in the client source file.

You can also include these optional header files:

You must use scoped names to the CORBA IDL module, the Jaguar SessionManager IDL module, and any component IDL modules that you want to execute methods on. To make using scoped names easier, you can use the C++ using statement for the IDL module namespaces as in the following example:

using namespace CORBA;
using namespace SessionManager;

If your C++ compiler does not support namespaces, define the compiler macro JAG_NO_NAMESPACE when compiling your source files.

When you create an object, identify the object reference by appending _var to the object name. The ObjectName_var reference will be automatically released when it is deallocated or assigned a new object reference.

CORBA::is_nil(Object) can be used to verify that a specific interface is implemented by a component. For an example, see "Creating a Manager instance".

If you are returning result sets from components, you should also specify the TabularResults Jaguar IDL module with the using statement.

Instantiating stub instances

Before invoking methods on component instances, the client must connect to a Jaguar server and instantiate the components. Your code must perform these steps to create proxy instances:

Step

What it does

Detailed explanation

1

Initialize the CORBA ORB and create an ORB reference.

"Configure and initialize the ORB runtime"

2

Use the ORB reference to create a Manager instance for the Jaguar server.

"Creating a Manager instance"

3

Use the Manager instance to create a Session.

"Creating sessions"

4

Use the Session instance to create stub component instances.

"Creating stub instances"

5

Call the stub methods to remotely invoke component methods.

"Invoking methods"

Note   Except for the example in "Processing result sets", the same client source code is used as an example throughout this section. Only the parts relevant to each step are used.

Configure and initialize the ORB runtime

Before you can use any ORB classes, you must call the ORB_init method, which:

Example: ORB initialization

ORB initialization is demonstrated in this example. You can specify the ORB options as a command line parameters to be passed to the ORB_init method.

#include <stdio.h>
#include <iostream.h>
#include <string.h>
#include <SessionManager.hpp>
#include <CosNaming.hpp>
#include <Jaguar.hpp>
#include <Tutorial.hpp> // Stubs for interfaces in Tutorial IDL
// module.

int main(int argc, char** argv)
{
const char *usage =
"Usage:\n\tarith -ORBNameServiceURL iiop://
<host>:<iiop-port>/<initial-context>\n";
const char *tutorial_help =
"Check Jaguar Manager and verify that the"
"Tutorial/CPPArithmetic component exists "
"and that it implements the "
"Tutorial::CPPArithmetic IDL interface.";

const char *ior_prefix = "iiop://";
const char *component_name = "Tutorial/CPPArithmetic";
char *ior = NULL;

try {

cout << "Creating Jaguar session\n\n";

// Initialize the ORB
CORBA::ORB_var orb = CORBA::ORB_init(argc, argv, 0);

Creating a Manager instance

The SessionManager::Manager interface is used for interacting with a Jaguar server. To create a Manager instance, you must identify the Jaguar server by using:

The IOR string encodes the server's host address and the port at which the server accepts IIOP requests. Each time a Jaguar server is started, for each listener the Jaguar server prints a hex-encoded IOR string with standard encoding to the following files in the Jaguar HTML subdirectory:

<listener> is the name of the listener.

<iiop-version> is the version of IIOP and can be either 10, which represents IIOP version 1.0, or 11, which represents IIOP version 1.1.

For example, a Jaguar server will generate the following files for a listener, iiops2:

You can code your C++ client to retrieve the IOR string from one of the <listener><iiop-version>.ior files.

The server's IIOP port is configured in Jaguar Manager using listeners. In the default configuration, the IIOP port number is 9000. (See "Verifying Your Environment" in Jaguar CTS Getting Started for more information on these files.)

Once the client has obtained the server's IOR or URL string, it calls the ORB::string_to_object method to convert the IOR or URL string into a Manager instance, as shown in the following example. You use the Manager::_narrow method to return a new object reference for the existing object, which is the IOR object.

  ...
Object_var object = orb->string_to_object ("iiop://myhost:9000");
Manager_var manager = Manager::_narrow (object);
if (is_nil(manager)) {
cout << "Error: Null SessionManager::Manager instance. Exiting.";
return -1;
}...

string_to_object returns an object reference to the URL, iiop://jagpc3:9000 , as object. For each reference, the _var form is used because the object will be automatically released when it is deallocated or assigned a new object reference. _narrow converts object into object reference for Manager.

_narrow returns a nil object reference if the component does not implement the interface. is_nil(manager) verifies that the SessionManager::Manager interface is implemented and returns an error if the interface is not implemented.

Creating sessions

The SessionManager::Session interface represents an authenticated session between the client application and a Jaguar server. The Manager::createSession method accepts a user name and password and returns a Session_var object, session, as shown in the example below:

  ...
Session_var session = manager.createSession("jagadmin", "");
...

Creating stub instances

You call the Session::lookup method to return a factory for proxy object references. The signature of Session::lookup is:

SessionManager::Factory_var lookup("name")

Session::lookup takes a string that specifies the name of the component to instantiate. A component's default name is the Jaguar package name and the component name, separated by a slash as in calculator/calc. However, a different name can be specified with the component's com.sybase.jaguar.component.naming property. For example, you can specify a logical name, such as USA/MyCompany/FinanceServer/Payroll. For more information on configuring the naming service, see "Jaguar Naming Services" in the Jaguar CTS System Administration Guide.

Session::lookup returns a factory for component proxies. Call the Factory::create method to obtain proxies for the component. This method returns a org.omg.CORBA.Object reference. Call _narrow to convert the object reference into an instance of the stub class for the component.

The code to call Session::factory and Factory::create looks like this:

  ...
// In this example, the component is named
// Repository and is installed in
// the Jaguar package.

Object_var obj = session->lookup("Jaguar/Repository");
SessionManager::Factory_var repoFactory =
SessionManager::Factory::_narrow(obj);

obj = repoFactory->create();
Jaguar::Repository_var repository =
Jaguar::Repository::_narrow(obj);

// Verify that we really have an instance.
if (CORBA::is_nil(repository))
{
cout << "ERROR: Null instance for component.";
}

Invoking methods

After instantiating the stub class, use the stub class instance to invoke the component's methods. The stub class has methods that correspond to each method in the component. Parameter datatypes are mapped as described in Table 15-1. Any parameter datatype can be used as a return type; in addition, user-defined IDL datatypes can be used as return, in, inout, or out parameters.

You can overload methods in C++ and Java, but not in ActiveX components. See "Operation declarations" and "Supported datatypes".

In addition to the tasks described in this section, you can also explicitly manage OTS transactions from your client. See "Managing explicit OTS transactions" for more information.

Processing result sets

To retrieve and process a single result set from a component:

  1. Call the component method on the stub instance that returns a result set.
  2. Iterate through each row and then each column in a row by using nested for loops.
  3. Use the discriminator method (_d) to retrieve the datatype of the column in a row and switch/case syntax to process the column values (such as printing the column values).

To retrieve and process multiple result sets returned from a component method as a TabularResults::ResultSets object:

  1. Call the component method on the component reference that returns the result sets.
  2. Retrieve the length or number of result sets.
  3. Iterate through the result sets using a for loop.
    For each result set, iterate through each row and then each column in a row by using nested for loops.

    You can treat a ResultSets object as an array of ResultSet objects. On each iteration, retrieve a reference to each ResultSet object by using the subscript [ ] operator.
  4. Use the discriminator method (_d) to retrieve the datatype of the column in a row and switch/case syntax to process the column values (such as printing the column values).

Example of processing result sets

This example retrieves a single result set. The following code shows the C++ client in its entirety. For detailed explanations, see the sections that explain each result-set processing step.

All of the required header files are included. The IDL module namespaces are specified with the C++ using statement. The printResultSet() method contains the logic for processing a result set. main() contains the logic to initialize and connect to the Jaguar ORB, instantiate the stub, call the component method to retrieve the result set object, and call printResultSet() to process the result set.

After the result set has been processed, execution of printResultSet() ends and control is returned to main(). In main(), the screen is kept open with the fprintf statement. Once you press Return, execution ends.

#include <stdio.h>
#include <time.h>
#include <iostream.h>
#include <SessionManager.hpp>
#include <TabularResults.hpp>
#include <Test.hpp>
using namespace CORBA;
using namespace SessionManager;
using namespace TabularResults;
using namespace Test;
void printResultSet(const ResultSet& rs)
{
ULong nc = rs.columns.length();
cout << rs.rows << " rows, " << nc << " columns" << endl;
for (ULong row = 0; row < rs.rows; row++)
{
cout << "row " << row << ": ";
for (ULong column = 0; column < nc; column++)
{
if (column > 0)
{
cout << ", ";
}
BooleanSeq& nulls = ((ColumnSeq&)rs.columns)[column].nulls;
if (row + 1 <= nulls.length() && nulls[row])
{
cout << "null";
continue;
}
Data& values = ((ColumnSeq&)rs.columns)[column].values;
switch (values._d())
{
case TYPE_BIT:
{
BooleanSeq& booleanValues = values.booleanValues();
cout << (booleanValues[row] ? "true" : "false");
break;
}
case TYPE_TINYINT:
{
OctetSeq octetValues = values.octetValues();
cout << octetValues[row];
break;
}
case TYPE_SMALLINT:
{
ShortSeq& shortValues = values.shortValues();
cout << shortValues[row];
break;
}
case TYPE_INTEGER:
{
LongSeq& longValues = values.longValues();
cout << longValues[row];
break;
}
case TYPE_REAL:
{
FloatSeq& floatValues = values.floatValues();
cout << floatValues[row];
break;
}
case TYPE_DOUBLE:
case TYPE_FLOAT:
{
DoubleSeq& doubleValues = values.doubleValues();
cout << doubleValues[row];
break;
}
case TYPE_CHAR:
case TYPE_LONGVARCHAR:
case TYPE_VARCHAR:
{
StringSeq& stringValues = values.stringValues();
cout << stringValues[row];
break;
}
case TYPE_BINARY:
case TYPE_LONGVARBINARY:
case TYPE_VARBINARY:
{
BinarySeq& binaryValues = values.binaryValues();
cout << "(binary)";
break;
}
case TYPE_BIGINT:
case TYPE_DECIMAL:
case TYPE_NUMERIC:
{
DecimalSeq& decimalValues = values.decimalValues();
cout << "(decimal)";
break;
}
case TYPE_DATE:
{
DateSeq& dateValues = values.dateValues();
// Assumption: time_t is seconds from Jan 1, 1970
time_t t = (time_t)((dateValues[row].dateValue - 40222.0) *
86400);
cout << ctime(&t);
break;
}
case TYPE_TIME:
{
TimeSeq& timeValues = values.timeValues();
cout << "time: " << timeValues[row].timeValue;
break;
}
case TYPE_TIMESTAMP:
{
TimestampSeq& timestampValues = values.timestampValues();
time_t t = (time_t)((timestampValues[row].dateValue +
timestampValues[row].timeValue - 40222.0) * 86400);
cout << ctime(&t);
break;
}
}
}
cout << endl;
}
}
int main(int argc, char** argv)
{
ORB_var orb = ORB_init(argc, argv, "");
Manager_var manager = Manager::
_narrow(Object_var(orb->string_to_object("iiop://myhost:9000")));
Session_var session = manager->createSession("jagadmin", "");
Ping_var p = Ping::_narrow(Object_var(session->create("Test/Java")));
ResultSet_var rs = p->results();
printResultSet(rs.in());
{
char c;
fprintf(stderr, "Press Return to continue...");
c = getchar();
}
return 0;
}

Retrieving the result set

To retrieve the result set, you must instantiate the stub and call the component method that returns a result set to the client. This example instantiates the stub from the Java component in the Test package in a session as an object p of type Ping_var using the _narrow method. The component method, results() is called on p which returns the result set rs.

 Ping_var p = Ping::_narrow(Object_var(session->create("Test/Java"))); 
ResultSet_var rs = p->results();

Iterating through the rows and columns

You must process each column value of each row one at a time. In this example, the processing is contained in a method (which you can reuse in other applications) called printResultSet(). printResultSet() takes the result set rs as an input parameter.

  printResultSet(rs.in()); 

The method uses the length() method to determine how many columns, nc, are in the result set, rs, and displays the number of columns and rows; the number of rows is represented by the variable rows. The method uses a for loop to iterate through each row, row, in the result set; and a nested for loop to iterate through each column, column, in the current row. The method must check for null values before it can process and print the values in each of the columns of the current row. After checking for and printing out null values, the method continues to the next column in the current row.

void printResultSet(const ResultSet& rs)
{
ULong nc = rs.columns.length();
cout << rs.rows << " rows, " << nc << " columns" << endl;
for (ULong row = 0; row < rs.rows; row++)
{
cout << "row " << row << ": ";
for (ULong column = 0; column < nc; column++)
{
if (column > 0)
{
cout << ", ";
}
BooleanSeq& nulls = ((ColumnSeq&)rs.columns)[column].nulls;

if (row + 1 <= nulls.length() && nulls[row])
{
cout << "null";
continue;
}

Retrieving the column datatype and processing values

In the body of printResultSet(), the _d() method (the discriminator method) is used to retrieve the datatype of the column and switch/case processing is used to process the column value in the current row. values is a reference to a Data object that represents the column value. _d() returns the datatype of the referenced value to the switch statement and the body of the case statement that matches the datatype is executed. In each case, the current row's column value that corresponds to the case's datatype is printed.

For the Date, Time, Timestamp datatypes, some conversion is required to print a value in a standard format (such as "January 5, 1998").

 Data& values = ((ColumnSeq&)rs.columns)[column].values; 
switch (values._d())
{
case TYPE_BIT:
{
BooleanSeq& booleanValues = values.booleanValues();
cout << (booleanValues[row] ? "true" : "false");
break;
}
case TYPE_TINYINT:
{
OctetSeq octetValues = values.octetValues();
cout << octetValues[row];
break;
}
case TYPE_SMALLINT:
{
ShortSeq& shortValues = values.shortValues();
cout << shortValues[row];
break;
}
case TYPE_INTEGER:
{
LongSeq& longValues = values.longValues();
cout << longValues[row];
break;
}
case TYPE_REAL:
{
FloatSeq& floatValues = values.floatValues();
cout << floatValues[row];
break;
}
case TYPE_DOUBLE:
case TYPE_FLOAT:
{
DoubleSeq& doubleValues = values.doubleValues();
cout << doubleValues[row];
break;
}
case TYPE_CHAR:
case TYPE_LONGVARCHAR:
case TYPE_VARCHAR:
{
StringSeq& stringValues = values.stringValues();
cout << stringValues[row];
break;
}
case TYPE_BINARY:
case TYPE_LONGVARBINARY:
case TYPE_VARBINARY:
{
BinarySeq& binaryValues = values.binaryValues();
cout << "(binary)";
break;
}
case TYPE_BIGINT:
case TYPE_DECIMAL:
case TYPE_NUMERIC:
{
DecimalSeq& decimalValues = values.decimalValues();
cout << "(decimal)";
break;
}
case TYPE_DATE:
{
DateSeq& dateValues = values.dateValues();
// Assumption: time_t is seconds from Jan 1, 1970
time_t t = (time_t)((dateValues[row].dateValue - 40222.0) *
86400);
cout << ctime(&t);
break;
}
case TYPE_TIME:
{
TimeSeq& timeValues = values.timeValues();
cout << "time: " << timeValues[row].timeValue;
break;
}
case TYPE_TIMESTAMP:
{
TimestampSeq& timestampValues = values.timestampValues();
time_t t = (time_t)((timestampValues[row].dateValue +
timestampValues[row].timeValue - 40222.0) * 86400);
cout << ctime(&t);
break;
}
}
}
cout << endl;
}
}

Handling exceptions

The client-side ORB throws two kinds of exceptions:

CORBA system exceptions

The CORBA specification defines the list of standard system exceptions. In C++, all CORBA system exceptions are mapped to a C++ class that is derived from the standard SystemException class defined in the CORBA module. You may want to trap the exceptions shown in this code fragment:

try
{
... // invoke methods
}
catch (CORBA::COMM_FAILURE& cf)
{
... // A component aborted the Jaguar transaction,
// or the transaction timed out. Retry the
// transaction if desired.
}
catch (CORBA::TRANSACTION_ROLLEDBACK& tr)
{
... // possibly retry the transaction
}
catch (CORBA::OBJECT_NOT_EXIST& one)
{
... // Received when trying to instantiate
// a component that does not exist. Also
// received when invoking a method if the
// object reference has expired
// (this can happen if the component
// is stateful and is configured with
// a finite Instance Timeout property).
// Create a new proxy instance if desired.}
}
catch (CORBA::NO_PERMISSSION& np)
{
... // tell the user they are not authorized
}
catch (CORBA::SystemException& se)
{
... // report the error but don't bother retrying
}

Note   Not all of the possible system exceptions are shown in the example. See the CORBA/IIOP 2.2 Specification (formal/98-02-01) for a list of all the possible exceptions.

User-defined exceptions

In C++, all CORBA user-defined exceptions are mapped to a C++ class that is derived from the standard UserException class defined in the CORBA module. For more information, see "User-defined IDL datatypes" and "User-defined exceptions".

Note   User-defined types must exist in the Jaguar IDL repository before you can use them in interface declarations.

 


Copyright © 2000 Sybase, Inc. All rights reserved.