Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Advanced CORBA Programming wit C++ - M. Henning, S. Vinoski.pdf
Скачиваний:
65
Добавлен:
24.05.2014
Размер:
5 Mб
Скачать

IT-SC book: Advanced CORBA® Programming with C++

Chapter 8. Developing a Client for the Climate Control System

8.1 Chapter Overview

This chapter presents the complete source code for a client that exercises the climate

control system. After the introduction, Section 8.3 outlines the overall structure of the code. Sections 8.4 to 8.6 develop the details of the source code, and Section 8.7 lists the source code for the complete client. Section 8.8 summarizes the advantages of writing clients with CORBA.

8.2 Introduction

We have now reached the point when we are ready to put together a complete client. Even though the client-side mapping contains a lot of detail and complexity, it is easy to write a client because judicious use of _var types removes most of the complexity. In addition, much of the client source is boiler-plate code that is the same for all clients. You can write such code once, put it in a library, and forget about it thereafter.

Before you read further, you may want to review the IDL for the climate control system at the end of Chapter 5.

8.3 Overall Client Structure

The client code has the following overall structure:

int

main(int argc, char * argv[])

{

try {

//Client code here...

}catch (const CORBA::Exception & e) {

cerr < "Uncaught CORBA exception: " < e < endl; return 1;

} catch (...) { return 1;

}

return 0;

}

The client code uses a main function with a try block as its body, and the entire client code is enclosed by that try block. This arrangement has two advantages.

293

IT-SC book: Advanced CORBA® Programming with C++

If an operation raises a CORBA exception that was not anticipated at the point of call, the catch handler for CORBA::Exception prints the name of the unexpected exception on stderr and terminates the program with non-zero exit status. If something goes unexpectedly wrong, at least we see the name of the exception and get orderly termination of the client. Generic output of exceptions as shown relies on your ORB to provide an ostream inserter for exceptions as a value-added feature. If the inserter is not provided by your ORB, you can use the one we show in Section 7.15.6.

All other exceptions are caught by the default catch handler. This has the advantage that we can throw any exception other than a CORBA exception from anywhere in the client and achieve clean termination by returning from main with non-zero exit status.

In general, it is a good idea to return from main instead of calling exit or _exit. exit calls only global destructors, and _exit terminates the program immediately without calling either local or global destructors.

Because destructors typically deallocate memory, "brute force" termination via exit or _exit causes problems in environments where resource recovery is not guaranteed by the operating system, such as in Windows or in an embedded system. In addition, if destructors are not called on program termination, memory debugging tools become less useful: they will report memory as still in use that otherwise would have been deallocated correctly.

8.4 Included Files

The client code begins by including some essential header files:

#include <iostream.h>

#include "CCS.hh" // ORB-specific

The interesting file here is CCS.hh. This is the header file generated by the IDL compiler for the client side from the CCS.idl definition file. It contains all the type definitions and proxy class declarations required by the C++ mapping. The exact name of this file is not specified by CORBA and therefore is vendor-specific. However, it is still easy to write vendor-independent code because most IDL compilers allow you to control the names of the generated files with a command-line option.

If you need to write ORB-independent code and if some of your IDL compilers do not provide such an option, all is not lost: you can achieve the same thing by making a generic include file. For example, assume that the generated file name is CCS.hh for one vendor and is CCSTypes.hh for another vendor; also assume that you cannot influence the choice of names. In this case, you can create a generic include file with a fixed name that conditionally includes the vendor-specific header:

//File: CCS_client.hh

//Generic client-side include file for all ORBs.

294

IT-SC book: Advanced CORBA® Programming with C++

#if defined(VENDOR_A) #include "CCS.hh" #elif defined(VENDOR_B) #include "CCSTypes.hh" #else

#error "Vendor not defined" #endif

This simple trick makes minor source incompatibilities an issue that affects only your build environment. That is better than having conditional include directives directly in your source files.

Alternatively, you can define a macro on the compile command line, such as '- DCCS_STUB_HDR="CCS.hh"', and then use

#include CCS_STUB_HDR

8.5 Helper Functions

The client contains a number of helper functions to keep the main code logic comprehensible.

8.5.1 Displaying Device Details

Our client exercises the climate control system by making a number of state changes to devices and printing the updated state. This means that we need a helper function that can show the details of a thermometer or thermostat. We do this by defining an overloaded ostream inserter that prints the details of a device given an object reference:

// Show the details for a thermometer or thermostat.

static ostream &

operator<(ostream & os, CCS::Thermometer_ptr t)

{

// Check for nil.

if (CORBA::is_nil(t)) {

os < "Cannot show state for nil reference." < endl; return os;

}

//Try to narrow and print what kind of device it is. CCS::Thermostat_var tmstat = CCS::Thermostat::_narrow(t);

os < (CORBA::is_nil(tmstat) ? "Thermometer:" : " Thermostat:") < endl;

//Show attribute values.

CCS::ModelType_var model = t->model();

295

IT-SC book: Advanced CORBA® Programming with C++

CCS::LocType_var location

= t->location();

os < "\tAsset number: " <

t->asset_num() < endl;

os < "\tModel

: " <

model < endl;

os < "\tLocation

: " <

location < endl;

os < "\tTemperature : " < t->temperature() < endl;

// If device is a thermostat, show nominal temperature. if (!CORBA::is_nil(tmstat))

os < "\tNominal temp: " < tmstat->get_nomin al() < endl; return os;

}

Given this helper function, the client can show the details of a thermometer or thermostat by inserting an object reference into an ostream. For example:

CCS::Thermometer_var tmv = ...;

CCS::Thermostat_ptr tsp = ...;

// Show details of both devices. cout < tmv;

cout < tsp;

It is worthwhile to examine the implementation of this helper function in more detail.

static ostream &

operator<(ostream & os, CCS::Thermometer_ptr t)

{

// ...

}

Note that the formal parameter type is CCS::Thermometer_ptr. This has two advantages.

We can pass either a _ptr reference or a _var reference to the helper function because _var references have an automatic conversion operator to _ptr references.

We can pass either a thermometer or a thermostat reference. This is because Thermometer is a base interface for Thermostat, so we can pass a thermostat reference when a thermometer reference is expected.

The first step of the helper function is to ensure that the passed reference is not nil because invoking an operation on a nil reference is illegal. The function then determines the actual type of the passed reference by calling _narrow:

// Check for nil.

if (CORBA::is_nil(t)) {

os < "Cannot show state for nil reference." < endl; return os;

}

// Try to narrow and print what kind of device it is.

296

IT-SC book: Advanced CORBA® Programming with C++

CCS::Thermostat_var tmstat = CCS::Thermostat::_narrow (t);

os < (CORBA::is_nil(tmstat) ? "Thermometer:" : "Ther mostat:") < endl;

This prints the heading "Thermometer:" or "Thermostat:" depending on the actual type of the device. Note that we catch the return value from _narrow in a _var reference because _narrow returns a copy that must be released.

The next few lines read and print the attribute values of the device. Because all these attributes are in the Thermometer base interface, it does not matter whether the passed reference denotes a thermometer or a thermostat. Note that we are using _var types for the model and location strings to avoid leaking memory:

// Show attribute values. CCS::ModelType_var model = t->model(); CCS::LocType_var location = t->location();

os < "\tAsset number: " < t->asset_num() < endl;

os < "\tModel

: " < model < endl;

os < "\tLocation

: " < location < endl;

os < "\tTemperature

: " < t->temperature() < endl;

The final remote call reads the nominal temperature of a thermostat. Because only thermostats support the get_nominal operation, we must invoke the operation on the thermostat reference we narrowed earlier. (We cannot use the thermometer reference t passed to the function because a thermometer proxy does not have a get_nominal member function.) We read the nominal temperature only if the previous call to _narrow succeeded—that is, if the thermostat reference is non-nil:

// If device is a thermostat, show nominal temperature. if (!CORBA::is_nil(tmstat))

os < "\tNominal temp: " < tmstat->get_nominal() < endl;

Any of the preceding remote calls may fail and raise a system exception. If that happens, control is transferred to the catch handler for CORBA::Exception at the end of main, which prints an error message and terminates the program with non-zero exit status.

8.5.2 Printing Error Exception Information

The client code deliberately provokes BadTemp and EChange exceptions when it exercises the server. To show the information contained in these exceptions, we define another ostream inserter that prints the details of a BtData structure. (This structure is a member of both BadTemp and EChange exceptions, so this helper function is useful for both exceptions.)

// Show the information in a BtData struct.

297

IT-SC book: Advanced CORBA® Programming with C++

static ostream &

operator<(ostream & os, const CCS::Thermostat::BtDat a & btd)

{

os < "CCS::Thermostat::BtData details:" < endl;

os < "\trequested

: "

<

btd.requested < endl;

os < "\tmin_permitted: "

<

btd.min_permitted < endl;

os < "\tmax_permitted: "

<

btd.max_permitted < endl;

os < "\terror_msg

: " < btd.error_msg < endl;

return os;

 

 

 

}

The function simply expects a reference to a BtData structure and prints each structure member on the specified ostream.

Showing the full details of an EChange exception requires a bit more work. Recall the relevant IDL:

// ...

interface Thermostat : Thermometer {

struct BtData {

requested;

TempType

TempType

min_permitted;

TempType

max_permitted;

string

error_msg;

};

exception BadTemp { BtData details; }; // ...

};

interface Controller { // ...

struct ErrorDetails {

Thermostat tmstat_ref; Thermostat::BtData info;

};

typedef sequence<ErrorDetails> ErrSeq;

exception EChange { ErrSeq errors;

}; // ...

}; // ...

The EChange exception contains a single data member errors, which is a sequence. Each sequence element in turn is a structure containing the object reference of the thermostat that could not make a temperature change in the tmstat_ref member, together with the exception information returned by that thermostat's set_nominal operation in the info member. We define another ostream inserter that prints the contents of an EChange exception:

// Loop over the sequence of records in an EChange exception and

298