
- •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++
try {
ctrl = CCS::Controller::_narrow(obj);
}
catch (const CORBA::Exception & e) {
cerr < "Cannot narrow controller reference: " < e < endl; throw 0;
}
if (CORBA::is_nil(ctrl)) {
cerr << "Controller reference has wrong type" << endl; throw 0;
}
// Controller reference is ready to use now...
We show the code with full error handling here. If anything unexpected happens, we print an error message and then throw zero. This technique relies on a catch handler higher up in the call chain to terminate the program cleanly or otherwise take corrective action.
Unfortunately, the specification for the Naming Service does not prohibit binding of a nil reference. This means that resolve can return nil without raising an exception.
The preceding code explicitly tests whether resolve returns a nil reference. This technique allows us to distinguish an advertised nil reference from one that is non-nil and fails to narrow to CCS::Controller. We could have omitted the first test for nil, in which case the code would detect a nil reference following the call to _narrow (but would produce an incorrect error message).
18.7 Iterators
To have a complete interface to the Naming Service, it must be possible to list the bindings in a context. The Naming Service uses iterators for that purpose.
18.7.1 The Need for Iterators
Naming contexts provide a list operation that allows you to retrieve the bindings stored in a context (list is analogous to the UNIX ls command). Before we discuss how list is defined, we first examine a more general problem. This problem is not specific to CORBA but occurs in any synchronous RPC system. Here is the problem statement.
Given a remote collection of items, in which the number of items in the collection is potentially unlimited, how can we list the contents of the collection?
This question is deceptively simple, but it raises a number of important design issues. To illustrate this, let us look at a naive version of a list operation on a collection of strings:
typedef sequence<string> StringList;
691

IT-SC book: Advanced CORBA® Programming with C++
interface StringCollection {
StringList list(); // Naive list operation // ...
};
If we invoke the list operation on a string collection, we simply receive all the strings in the collection as a sequence.
At first glance, our definition of list looks sensible, but it contains a problem: what if there is a very large number of strings in the collection? The entire sequence of strings must be buffered in memory during call dispatch, so eventually the number of strings will grow large enough for the operation to fail because of memory limitations.
The general solution to this problem is to create an iterator object for the client. An iterator object allows the client to retrieve results incrementally. Iterators have one of two styles of interface: a pull iterator or a push iterator.
18.7.2 Pull Iterators
Here is a simple version of a pull iterator:
typedef sequence<string> StringList;
interface StringIterator { StringList next(); void destroy();
};
interface StringCollection {
StringList list(out StringIterator it); // Better // ...
};
To read all the strings in the collection, we call the list operation on a StringCollection object. As with the naive version, the operation returns a sequence of strings as the return value.
If all the strings in the collection can fit onto the sequence without causing memory problems, the return value contains the complete collection. In addition, the out parameter it is nil.
If not all the strings in the collection can fit onto the sequence, the return value contains the first batch of strings. In addition, the operation creates an iterator object and returns a reference to the iterator in the parameter it.
The client uses the iterator object to incrementally retrieve the remaining strings by repeatedly calling next on the iterator. Each call to next returns the next batch of
692

IT-SC book: Advanced CORBA® Programming with C++
strings—for example, 100 strings at a time. When the collection is exhausted, next returns an empty sequence to indicate end-of-collection.
This approach solves the problem. If the collection is short enough, all its contents are returned by the initial call to list. If the collection is too large, the server creates an iterator object on behalf of the client, and the client uses the iterator object to retrieve the results. Between calls to next, the iterator object remembers the current reading position in the collection, so it knows which batch of strings to return next.
The destroy operation of the iterator allows the client to inform the server that it no longer wants to use the iterator. The client can call destroy before it has retrieved all of the collection.
Here is example code that shows how a client can iterate over a string collection:
StringCollection_var sc = ...; |
|
// Get reference... |
StringList_var sl; |
|
|
StringIterator_var it; |
|
// Get first batch |
sl = sc->list(it); |
|
|
CORBA::ULong i; |
|
// Show first batch |
for (i = 0; i < sl->length(); i++) |
||
cout < sl[i] < endl; |
|
|
if (!CORBA::is_nil(it)) { |
|
// More to come? |
do { |
// Get next batch |
|
sl = it->next(); |
||
for (i = 0; i < sl->length(); i++) // Show it |
||
cout < sl[i] < endl; |
|
|
} while (sl->length() != 0); |
|
// Clean up |
it->destroy(); |
|
|
} |
|
|
There are many variations on the iterator IDL we just discussed. (As you will see in Section 18.7.4, iterators for the Naming Service add some additional features.) The general style of interaction is that of a pull iterator because the receiver of the collection (the client) "pulls" the contents from the sender (the server) by invoking an operation.
18.7.3 Push Iterators
For push iterators, the client passes an iterator reference to the server, and the server invokes an operation on the iterator to deliver the contents of the collection. In other words, client and server roles are reversed; the receiver implements the iterator, and the sender of the collection "pushes" the collection into the receiver. Here is how we could define iteration for our string collection using a push model:
typedef sequence<string> StringList; interface StringIterator {
693

IT-SC book: Advanced CORBA® Programming with C++
void next(in StringList sl);
};
interface StringCollection {
StringList list(in StringIterator it); // Push iterator // ...
};
The client implements a StringIterator object and passes a reference to this iterator to the server in the initial call to list. Again, the return value from list is a sequence of strings.
If all the strings in the collection can fit onto the sequence without causing memory problems, the return value contains the complete collection. In addition, the server indicates that all of the sequence was delivered in the first call by invoking the next operation on the iterator, but it passes an empty sequence as the parameter sl to indicate end-of-collection.
If not all the strings in the collection can fit onto the sequence, the return value contains the first batch of strings. The server delivers the remainder of the collection by invoking next on the iterator to deliver the next batch. When all of the collection has been sent this way, the server calls next one more time with an empty sequence to indicate end- of-collection.
Push iterators are an application of the more general Callback pattern (see Section 20.3). However, they are rarely used because they force the client to also act as a server. This requirement complicates development because the client must run an event loop, and (depending on the ORB) the client may also need to be multithreaded to avoid deadlock. In addition, because IIOP is a unidirectional protocol, push iterators require that an extra connection be opened for the calls on the iterator. With a pull model, on the other hand, all interactions can take place over the same single connection. For these reasons, the Naming Service uses pull iterators.
18.7.4 Naming Service Iterators
Here is the IDL used by the Naming Service to give you access to the bindings in a context:
module CosNaming { // ...
enum BindingType { nobject, ncontext };
struct Binding {
Name binding_name; BindingType binding_type; };
typedef sequence<Binding> BindingList;
interface BindingIterator; // Forward declaration
694

IT-SC book: Advanced CORBA® Programming with C++
interface NamingContext { // ...
void list(
in unsigned long how_many, out BindingList bl,
out BindingIterator it
);
};
interface BindingIterator {
boolean next_one(out Binding b); boolean next_n(
in unsigned long how_many, out BindingList bl
);
void destroy();
};
};
list
The list call for the Naming Service follows the pattern for pull iterators. The initial batch of bindings is returned in the out parameter bl, and the out parameter it contains a reference to an iterator if not all bindings can be returned with the first call.
The how_many parameter allows you to specify the maximum number of bindings to be returned with the first call. A call to list is guaranteed to return no more than how_many bindings in the bl parameter. However, it may return fewer because the Naming Service may enforce a limit lower than the one you request with how_many. Setting how_many to zero permits you to retrieve all results via an iterator because it forces the initial result sequence to be empty.
If the call to list returns all the bindings in the context, the it iterator reference is nil. Otherwise, it points at an iterator of type BindingIterator that you can use to retrieve the remaining bindings.
next_n
The next_n operation on the iterator returns the next how_many bindings in the parameter bl. As with list, there may be fewer sequence elements in bl than you requested with how_many because the operation may choose, for example, to never return more than some fixed number of bindings. A value of zero for how_many raises a BAD_PARAM exception.
The return value from next_n tells you whether the bl parameter contains valid bindings. If this call to next_n returned bindings, the return value is TRUE. If this call
695

IT-SC book: Advanced CORBA® Programming with C++
to next_n returned no bindings, the return value is FALSE, and the value of bl is undefined (most likely the returned sequence will have zero elements).
next_one
The next_one operation returns a single binding at a time in the out parameter b. The return value indicates whether b contains a valid binding. If the return value is TRUE, b contains the next binding. If the return value is FALSE, iteration is complete and the value of b is undefined.
We recommend that you do not use next_one because it requires a remote call for every single binding. It is more efficient to use next_n to retrieve bindings in batches of 100 bindings or so. In addition, next_one is redundant because you can achieve the same thing by calling next_n with a how_many value of 1.
destroy
The destroy operation permanently destroys the iterator. You can call destroy at any time even before you have retrieved all bindings from the context. However, you must call destroy eventually even if you retrieve all bindings.
Interpreting a Binding List
As you saw on page 803, iterator operations return a BindingList:
enum BindingType { nobject, ncontext };
struct Binding {
Name binding_name; BindingType binding_type;
};
typedef sequence<Binding> BindingList;
Each binding in the sequence is a pair. The binding_name member of the Binding structure provides the name of the binding, and the binding_type member indicates the type of object denoted by the binding. If the type is ncontext, the object bound with the name is a naming context. If the type is nobject, the object is an ordinary application object (and therefore a leaf in the naming graph).
A binding list contains only names for the bindings immediately contained in the context; it does not contain bindings in subcontexts. For example, listing the app2 context in Figure 18.7 returns only the bindings devices and collections. As a result, the binding_name member of the Binding structure is always a sequence of length 1.
Iterating Over a Naming Context
696

IT-SC book: Advanced CORBA® Programming with C++
The following code example prints all bindings in a context. The logic to iterate over the context is contained in list_context, which prints the bindings contained in the context passed as the nc parameter. show_chunk is a simple helper function that prints the contents of a binding list:
void
show_chunk(const CosNaming::BindingList & bl) // Helper function
{
for (CORBA::ULong i = 0; i < bl.length(); i++) {
cout << bl[i].binding_name[0].id;
if (bl[i].binding_name[0].kind[0] != '\0')
cout << "(" << bl[i].binding_name[0].kind << ")"; if (bl[i].binding_type == CosNaming::ncontext)
cout << ": context" << endl; else
cout << ": reference" << endl;
}
}
void list_context(CosNaming::NamingContext_ptr nc)
{
CosNaming::BindingIterator_var it; |
// Iterator reference |
|
CosNaming::BindingList_var bl; |
// Binding list |
|
const CORBA::ULong CHUNK = 100; |
// Chunk size |
|
nc->list(CHUNK, bl, it); |
// Get first chunk |
|
show_chunk(bl); |
// Print first chunk |
|
if (!CORBA::is_nil(it)) { |
// More bindings? |
|
while (it->next_n(CHUNK, bl)) |
// Get next chunk |
|
show_chunk(bl); |
// Print chunk |
|
it->destroy(); |
// Clean up |
|
} |
|
|
}
This code prints each binding on a separate line. If the kind field is a non-empty string, it is shown in parentheses following the id field. Each line also shows the binding type. Here is some example output:
user(dir): context controller: reference thermostats: context thermometers: context
Only the user binding in this context has a non-empty kind field with value dir, and the remainder of the bindings use the empty string as the kind field. Also note that the output is not sorted—it is up to you to sort bindings for display purposes.
697

IT-SC book: Advanced CORBA® Programming with C++
To minimize the number of remote calls, list_context retrieves bindings in lots of 100. However, it does not rely on receiving exactly 100 bindings with each call. Instead, the length of the binding list is used to control the loop in show_chunk. This technique ensures that the code works correctly even if the Naming Service chooses to return no more than 50 bindings per call.
The second part of list_context is executed only if list returned an iterator. Note that we take care to call destroy on the iterator before returning.
Destroying Iterators
You must explicitly call destroy on an iterator object. If you do not call destroy, the Naming Service has no way of knowing when you are finished with the iterator. Consider a scenario in which a malicious client calls list repeatedly, creating an iterator object with each call, but never calls destroy on these iterators. The Naming Service creates more and more iterators for the client but never gets a chance to destroy them. Eventually, this leads to failure of the service or at least causes performance problems because of excessive memory consumption.
A high-quality implementation of the service will actively take steps to protect itself against this scenario. There are several ways in which a server can avoid running out of memory. For example, the server could place an upper limit on the total number of iterators that may exist at one time and refuse to create more iterators when that limit is exceeded. Alternatively, a server can monitor activity of its iterator objects and destroy any iterators that have not been used for some time.
The CORBA specification does not state exactly how a server should protect itself against "iterator pileup" (or that it must protect itself at all), so you should ask your vendor exactly how the service deals with this scenario. However, as a client to the service, it can happen to you (albeit rarely) that a perfectly good iterator stops working and that a call to next raises OBJECT_NOT_EXIST. In that case, the server probably found itself with too many iterators and destroyed the one you were using.
A high-quality implementation does not indiscriminately destroy iterators. Instead, it destroys those iterators that have been idle for a long time and are therefore likely to be no longer in use. However, a robust client should deal with an OBJECT_NOT_EXIST exception during iteration. The most likely recovery behavior is to restart iteration from the beginning.
This scenario is not limited to the Naming Service. In fact, it can arise whenever a server provides life cycle operations for objects. The problem is caused by the fact that the server creates an object on behalf of the client but relies on the client to eventually destroy the object. (This is similar to allocating memory in the callee and relying on the caller to deallocate it.)
698