
- •Advanced CORBA® Programming with C++
- •Review
- •Dedication
- •Preface
- •Prerequisites
- •Scope of this Book
- •Acknowledgments
- •Chapter 1. Introduction
- •1.1 Introduction
- •1.2 Organization of the Book
- •1.3 CORBA Version
- •1.4 Typographical Conventions
- •1.5 Source Code Examples
- •1.6 Vendor Dependencies
- •1.7 Contacting the Authors
- •Part I: Introduction to CORBA
- •Chapter 2. An Overview of CORBA
- •2.1 Introduction
- •2.2 The Object Management Group
- •2.3 Concepts and Terminology
- •2.4 CORBA Features
- •2.5 Request Invocation
- •2.6 General CORBA Application Development
- •2.7 Summary
- •Chapter 3. A Minimal CORBA Application
- •3.1 Chapter Overview
- •3.2 Writing and Compiling an IDL Definition
- •3.3 Writing and Compiling a Server
- •3.4 Writing and Compiling a Client
- •3.5 Running Client and Server
- •3.6 Summary
- •Part II: Core CORBA
- •Chapter 4. The OMG Interface Definition Language
- •4.1 Chapter Overview
- •4.2 Introduction
- •4.3 Compilation
- •4.4 Source Files
- •4.5 Lexical Rules
- •4.6 Basic IDL Types
- •4.7 User-Defined Types
- •4.8 Interfaces and Operations
- •4.9 User Exceptions
- •4.10 System Exceptions
- •4.11 System Exceptions or User Exceptions?
- •4.12 Oneway Operations
- •4.13 Contexts
- •4.14 Attributes
- •4.15 Modules
- •4.16 Forward Declarations
- •4.17 Inheritance
- •4.18 Names and Scoping
- •4.19 Repository Identifiers and pragma Directives
- •4.20 Standard Include Files
- •4.21 Recent IDL Extensions
- •4.22 Summary
- •Chapter 5. IDL for a Climate Control System
- •5.1 Chapter Overview
- •5.2 The Climate Control System
- •5.3 IDL for the Climate Control System
- •5.4 The Complete Specification
- •Chapter 6. Basic IDL-to-C++ Mapping
- •6.1 Chapter Overview
- •6.2 Introduction
- •6.3 Mapping for Identifiers
- •6.4 Mapping for Modules
- •6.5 The CORBA Module
- •6.6 Mapping for Basic Types
- •6.7 Mapping for Constants
- •6.8 Mapping for Enumerated Types
- •6.9 Variable-Length Types and _var Types
- •6.10 The String_var Wrapper Class
- •6.11 Mapping for Wide Strings
- •6.12 Mapping for Fixed-Point Types
- •6.13 Mapping for Structures
- •6.14 Mapping for Sequences
- •6.15 Mapping for Arrays
- •6.16 Mapping for Unions
- •6.17 Mapping for Recursive Structures and Unions
- •6.18 Mapping for Type Definitions
- •6.19 User-Defined Types and _var Classes
- •6.20 Summary
- •Chapter 7. Client-Side C++ Mapping
- •7.1 Chapter Overview
- •7.2 Introduction
- •7.3 Mapping for Interfaces
- •7.4 Object Reference Types
- •7.5 Life Cycle of Object References
- •7.6 Semantics of _ptr References
- •7.7 Pseudo-Objects
- •7.8 ORB Initialization
- •7.9 Initial References
- •7.10 Stringified References
- •7.11 The Object Pseudo-Interface
- •7.12 _var References
- •7.13 Mapping for Operations and Attributes
- •7.14 Parameter Passing Rules
- •7.15 Mapping for Exceptions
- •7.16 Mapping for Contexts
- •7.17 Summary
- •Chapter 8. Developing a Client for the Climate Control System
- •8.1 Chapter Overview
- •8.2 Introduction
- •8.3 Overall Client Structure
- •8.4 Included Files
- •8.5 Helper Functions
- •8.6 The main Program
- •8.7 The Complete Client Code
- •8.8 Summary
- •Chapter 9. Server-Side C++ Mapping
- •9.1 Chapter Overview
- •9.2 Introduction
- •9.3 Mapping for Interfaces
- •9.4 Servant Classes
- •9.5 Object Incarnation
- •9.6 Server main
- •9.7 Parameter Passing Rules
- •9.8 Raising Exceptions
- •9.9 Tie Classes
- •9.10 Summary
- •Chapter 10. Developing a Server for the Climate Control System
- •10.1 Chapter Overview
- •10.2 Introduction
- •10.3 The Instrument Control Protocol API
- •10.4 Designing the Thermometer Servant Class
- •10.5 Implementing the Thermometer Servant Class
- •10.6 Designing the Thermostat Servant Class
- •10.7 Implementing the Thermostat Servant Class
- •10.8 Designing the Controller Servant Class
- •10.9 Implementing the Controller Servant Class
- •10.10 Implementing the Server main Function
- •10.11 The Complete Server Code
- •10.12 Summary
- •Chapter 11. The Portable Object Adapter
- •11.1 Chapter Overview
- •11.2 Introduction
- •11.3 POA Fundamentals
- •11.4 POA Policies
- •11.5 POA Creation
- •11.6 Servant IDL Type
- •11.7 Object Creation and Activation
- •11.8 Reference, ObjectId, and Servant
- •11.9 Object Deactivation
- •11.10 Request Flow Control
- •11.11 ORB Event Handling
- •11.12 POA Activation
- •11.13 POA Destruction
- •11.14 Applying POA Policies
- •11.15 Summary
- •Chapter 12. Object Life Cycle
- •12.1 Chapter Overview
- •12.2 Introduction
- •12.3 Object Factories
- •12.4 Destroying, Copying, and Moving Objects
- •12.5 A Critique of the Life Cycle Service
- •12.6 The Evictor Pattern
- •12.7 Garbage Collection of Servants
- •12.8 Garbage Collection of CORBA Objects
- •12.9 Summary
- •Part III: CORBA Mechanisms
- •Chapter 13. GIOP, IIOP, and IORs
- •13.1 Chapter Overview
- •13.2 An Overview of GIOP
- •13.3 Common Data Representation
- •13.4 GIOP Message Formats
- •13.5 GIOP Connection Management
- •13.6 Detecting Disorderly Shutdown
- •13.7 An Overview of IIOP
- •13.8 Structure of an IOR
- •13.9 Bidirectional IIOP
- •13.10 Summary
- •14.1 Chapter Overview
- •14.2 Binding Modes
- •14.3 Direct Binding
- •14.4 Indirect Binding via an Implementation Repository
- •14.5 Migration, Reliability, Performance, and Scalability
- •14.6 Activation Modes
- •14.7 Race Conditions
- •14.8 Security Considerations
- •14.9 Summary
- •Part VI: Dynamic CORBA
- •Chapter 15 C++ Mapping for Type any
- •15.1 Chapter Overview
- •15.2 Introduction
- •15.3 Type any C++ Mapping
- •15.4 Pitfalls in Type Definitions
- •15.5 Summary
- •Chapter 16. Type Codes
- •16.1 Chapter Overview
- •16.2 Introduction
- •16.3 The TypeCode Pseudo-Object
- •16.4 C++ Mapping for the TypeCode Pseudo-Object
- •16.5 Type Code Comparisons
- •16.6 Type Code Constants
- •16.7 Type Code Comparison for Type any
- •16.8 Creating Type Codes Dynamically
- •16.9 Summary
- •Chapter 17. Type DynAny
- •17.1 Chapter Overview
- •17.2 Introduction
- •17.3 The DynAny Interface
- •17.4 C++ Mapping for DynAny
- •17.5 Using DynAny for Generic Display
- •17.6 Obtaining Type Information
- •17.7 Summary
- •Part V: CORBAservices
- •Chapter 18. The OMG Naming Service
- •18.1 Chapter Overview
- •18.2 Introduction
- •18.3 Basic Concepts
- •18.4 Structure of the Naming Service IDL
- •18.5 Semantics of Names
- •18.6 Naming Context IDL
- •18.7 Iterators
- •18.8 Pitfalls in the Naming Service
- •18.9 The Names Library
- •18.10 Naming Service Tools
- •18.11 What to Advertise
- •18.12 When to Advertise
- •18.13 Federated Naming
- •18.14 Adding Naming to the Climate Control System
- •18.15 Summary
- •Chapter 19. The OMG Trading Service
- •19.1 Chapter Overview
- •19.2 Introduction
- •19.3 Trading Concepts and Terminology
- •19.4 IDL Overview
- •19.5 The Service Type Repository
- •19.6 The Trader Interfaces
- •19.7 Exporting Service Offers
- •19.8 Withdrawing Service Offers
- •19.9 Modifying Service Offers
- •19.10 The Trader Constraint Language
- •19.11 Importing Service Offers
- •19.12 Bulk Withdrawal
- •19.13 The Admin Interface
- •19.14 Inspecting Service Offers
- •19.15 Exporting Dynamic Properties
- •19.16 Trader Federation
- •19.17 Trader Tools
- •19.18 Architectural Considerations
- •19.19 What to Advertise
- •19.20 Avoiding Duplicate Service Offers
- •19.21 Adding Trading to the Climate Control System
- •19.22 Summary
- •Chapter 20. The OMG Event Service
- •20.1 Chapter Overview
- •20.2 Introduction
- •20.3 Distributed Callbacks
- •20.4 Event Service Basics
- •20.5 Event Service Interfaces
- •20.6 Implementing Consumers and Suppliers
- •20.7 Choosing an Event Model
- •20.8 Event Service Limitations
- •20.9 Summary
- •Part VI: Power CORBA
- •Chapter 21. Multithreaded Applications
- •21.1 Chapter Overview
- •21.2 Introduction
- •21.3 Motivation for Multithreaded Programs
- •21.4 Fundamentals of Multithreaded Servers
- •21.5 Multithreading Strategies
- •21.6 Implementing a Multithreaded Server
- •21.7 Servant Activators and the Evictor Pattern
- •21.8 Summary
- •22.1 Chapter Overview
- •22.2 Introduction
- •22.3 Reducing Messaging Overhead
- •22.4 Optimizing Server Implementations
- •22.5 Federating Services
- •22.6 Improving Physical Design
- •22.7 Summary
- •Appendix A. Source Code for the ICP Simulator
- •Appendix B. CORBA Resources
- •Bibliography

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