Chapter 17 Creating CORBA C++ Clients
These section describes how to code a CORBA C++ client that invokes component methods:
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:
TabularResults.hpp already includes BCD.hpp and MJD.hpp; if you include TabularResults.hpp, you do not have to include BCD.hpp and MJD.hpp.
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.
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:
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.
Before you can use any ORB classes, you must call the ORB_init method, which:
ORBWebProxyHost
is
true), this property specifies the port number at which the HTTP
proxy server accepts connections. There is no default for this property,
and you must specify both a host name and port. You can also specify
the property by setting the environment variable JAG_WEBPROXYPORT.ORBWebProxyHost
parameter).
You can also specify the property by setting the property JAG_HTTPEXTRAHEADER.User-agent: Jaguar/major.minorwhere major and minor are the major and minor version numbers of your Jaguar client software, respectively.
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);
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.
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", "");
...
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.";
}
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.
To retrieve and process a single result set from a component:
To retrieve and process multiple result sets returned from a component method as a TabularResults::ResultSets object:
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;
}
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();
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;
}
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;
}
}
The client-side ORB throws two kinds of 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
}
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.
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".
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. |