
- •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++
our life span policy object because it is no longer needed; the create_POA operation guarantees that it will make a copy of the objects in the policy list if it needs to. A newly created POA will therefore not hold references to the policy objects passed into create_POA but will instead refer to the copies.
Creating POAs with different policies and with combinations of policies is also possible. In fact, using some policies automatically limits your choices of the remaining policies because some of them imply the use of others and some of them are mutually exclusive. However, before we can describe some of the useful combinations of policies that can be passed to create_POA, we must fully explain an important feature of the
PortableServer module: the Servant IDL type.
11.6 Servant IDL Type
Objects are incarnated by servants. In Section 9.2 we describe servants as programming language entities that provide bodies, or implementations, for CORBA objects. In C++, a servant is an instance of a C++ class type.
Because the POA is fully specified in IDL, however, there must be a way to describe servants in IDL as well. Unfortunately, this requirement is at odds with the fact that servants are programming language entities. How can language-specific servants be described in IDL, which itself is independent of any programming language?
To solve this dilemma, the native keyword was added to IDL for version 2.2 of the CORBA specification. The purpose of the native keyword is to allow an IDL identifier to be declared as a type that has no IDL definition but is instead defined separately by each language mapping. Because each native type requires a separate definition in each IDL language mapping, only the OMG is allowed to add native declarations to IDL. Application developers who attempt to declare their own native types will likely discover that their IDL compilers refuse to compile their IDL.
The definition of the IDL Servant type is as follows:
module PortableServer { native Servant;
};
In C++, the Servant type maps to a pointer to the ServantBase class:
namespace PortableServer { class ServantBase { public:
virtual ~ServantBase();
virtual POA_ptr _default_POA();
virtual CORBA::InterfaceDef_ptr _get_interface()
397

IT-SC book: Advanced CORBA® Programming with C++
throw(CORBA::SystemException);
virtual CORBA::Boolean _is_a(
const char * logical_type_id ) throw(CORBA::SystemException);
virtual CORBA::Boolean
|
_non_existent() |
|
throw(CORBA::SystemException); |
virtual void |
_add_ref(); |
virtual void |
_remove_ref(); |
protected:
ServantBase();
ServantBase(const ServantBase & base); ServantBase & operator=(const ServantBase & base);
};
typedef ServantBase * Servant;
// ...
}
As we mention in Section 9.3, ServantBase serves as the base class for all skeletons and thus for all application servant classes as well. To ensure proper destruction of these derived classes, ServantBase has a public virtual destructor. All constructors provided by the ServantBase class are protected because it is intended as an abstract base class. ServantBase makes both the copy constructor and the default assignment operator available in case derived servant classes want to support copying.
ServantBase provides the _default_POA function. The ServantBase implementation of the _default_POA function returns a reference to the Root POA. The _default_POA function provides the POA reference when a servant's _this function is invoked to implicitly create and activate a new transient CORBA object. Because it is virtual, the _default_POA function can be overridden by derived servant classes to return a reference to a different POA. This allows the _this function to be used to create and activate transient objects in a POA other than the Root POA.
The _get_interface, _is_a, and _non_existent functions provide default implementations of these IDL operations inherited by all objects from CORBA::Object. By default, _get_interface and _is_a are overridden by each static skeleton type and are implemented in the generated code to return a result based on the skeleton's most-derived interface type. The default implementation of _non_existent returns false. Because these functions are virtual, you can override the default implementations in your derived servant classes if necessary. For example, you must override _non_existent to return the correct response for the target object whenever your servant incarnates multiple CORBA objects.
398

IT-SC book: Advanced CORBA® Programming with C++
The _add_ref and _remove_ref functions allow concrete servant classes to perform reference counting. Their default implementations do nothing, but they are virtual so that application developers can override them if they need to provide their own reference counting solutions. The PortableServer namespace also provides a thread-safe reference counting mix-in class called RefCountServantBase. Applications can derive their servants from this mix-in class to obtain reference-counting implementations of _add_ref and _remove_ref. We explain these functions in more detail in our coverage of servant memory management issues in Section 11.7.5.
11.6.1 CCS::Thermometer Servant
PortableServer::ServantBase and all skeleton classes derived from it are abstract base classes, so to create servants, applications must provide concrete servant classes that can be instantiated. The following example shows an application servant class for the CCS::Thermometer interface; it is similar to the one we show in Section
10.11. The only difference is that the constructor takes only the asset number of the device that the servant represents. Previously, the constructor also required a location string, which it used to directly program the location of the device. In this chapter, we instead make the Controller singleton object responsible for initializing all devices.
#include "CCSS.hh"
class Controller_impl;
class Thermometer_impl : public virtual POA_CCS::Thermometer { public:
Thermometer_impl(CCS::AssetType anum); virtual ~Thermometer_impl();
// Functions for the Thermometer attributes. virtual CCS::ModelType
model() throw(CORBA::SystemException);
virtual CCS::AssetType
asset_num() throw(CORBA::SystemException);
virtual CCS::TempType
temperature() throw(CORBA::SystemException);
virtual CCS::LocType
location() throw(CORBA::SystemException);
virtual void |
location(const char * loc) |
|
|
throw(CORBA::SystemException) ; |
|
static Controller_impl * m_ctrl; |
// My controller |
|
protected: |
|
// My asset number |
const CCS::AssetType m_anum; |
// Helper functions that read data from the device. CCS::ModelType get_model();
CCS::TempType get_temp();
399

IT-SC book: Advanced CORBA® Programming with C++
CCS::LocType |
get_loc(); |
void |
set_loc(const char * new_loc); |
private:
// copy not supported for this class Thermometer_impl(const Thermometer_impl & therm); void operator=(const Thermometer_impl & therm);
};
Just as we show in Section 10.4, our servant class, Thermometer_impl, derives from the POA_CCS::Thermometer skeleton class, which in turn derives from ServantBase. It overrides all the pure virtual functions it inherits from the POA_CCS::Thermometer skeleton, and it defines private helper functions that can be used to get and set state from the actual target device via the ICP network described in Section 10.3. It holds one data member: the asset number of the device it represents. The only differences in implementation between the Thermometer_impl class shown in Section 10.5 and the one shown here are in the constructor and destructor.
Thermometer_impl::
Thermometer_impl(CCS::AssetType anum) : m_anum(anum)
{
m_ctrl->add_impl(anum); |
// Add self to map. |
}
Thermometer_impl:: ~Thermometer_impl()
{
m_ctrl->remove_impl(m_anum);
}
Rather than make our Thermometer_impl constructor responsible for putting its device on-line and making the destructor take it off-line, we have moved those responsibilities to our Controller. As we show in Section 11.7.3, this modification allows us to create our servants on demand instead of creating them all up front.
In the next section we show how the definition of the Servant IDL type and its mapping to a ServantBase * allow instances of application-specific C++ servant classes, such as our Thermometer_impl class, to be used to activate and incarnate CORBA objects.
11.7 Object Creation and Activation
Naturally, a CORBA object must exist before it can have its operations invoked. After it is created, the object must be activated before it can handle any request invocations. The POA provides several options for creating objects and activating them by servant registration.
An application can create objects without creating any servants.
400

IT-SC book: Advanced CORBA® Programming with C++
An application can either implicitly or explicitly register a servant to incarnate an object and have the POA retain knowledge of the association between that object and its servant. An application can supply one of two types of servant manager objects that can dynamically supply servants on a per-request basis. The application can also choose either to have the POA retain the object-to-servant associations as they are supplied by the servant manager or to require that the servant manager be invoked separately for every request to obtain a servant.
An application can provide a default servant that will be used if the target object is not currently incarnated by any other servant.
We describe each of these options more fully in the sections that follow. Section 11.7.1 describes object creation, Section 11.7.2 explains explicit object activation, Section 11.7.3 details servant managers, and Section 11.7.4 explains default servants.
11.7.1 Object Creation
The POA provides two operations for creating CORBA objects without creating servants.
module PortableServer {
typedef sequence<octet> ObjectId;
interface POA {
Object create_reference(
in CORBA::RepositoryId intf ) raises(WrongPolicy);
Object create_reference_with_id(
in ObjectId oid, in CORBA::RepositoryId intf
) raises(WrongPolicy);
// ...
};
};
Both create_reference and create_reference_with_id require a
CORBA::RepositoryId argument to identify the most derived IDL interface that the new object will support. If the most derived interface has any base interfaces, the new object will also support them. The results of passing a repository ID that does not identify the most derived interface of the object are undefined and therefore non-portable.
The create_reference operation requires the POA to have an IdAssignmentPolicy value of SYSTEM_ID, allowing the POA to generate an ObjectId for the new object. If the POA does not have this policy value, create_reference raises the WrongPolicy exception.
401

IT-SC book: Advanced CORBA® Programming with C++
When using create_reference_with_id, on the other hand, the application supplies the ObjectId. This ObjectId signifies the identity of the object in the application domain. For example, it might consist of a pathname to a file the application uses to persistently store the object's state, or it could be composed of a database key or an account number. Because the ObjectId type is a sequence of octet, it can contain almost anything you find meaningful to identify your objects. The ObjectId instances for the following examples consist of the asset number of the device represented by the CORBA object.
Calling create_reference_with_id multiple times with the same ObjectId and the same repository ID is legal, but the results may vary among POA implementations. Some POAs may return the same object reference each time, whereas others might return distinct object references for each create_reference_with_id invocation. Despite these possible differences, keep in mind that the POA dispatches requests to your servants based solely on ObjectId. This means that the POA will dispatch requests to the right servant whether it returns the same or returns distinct object references when you call create_reference_with_id multiple times with the same arguments. It might seem odd that there is nothing to prevent you from passing an entirely different repository ID on subsequent invocations of create_reference_with_id with the same ObjectId value. The POA will not raise an exception if you do so because the cost of having the POA detect this irregular usage would be extremely high, essentially requiring each POA to somehow remember all object references it ever created. It is therefore up to you to guarantee this consistency for your applications.
If the POA has the SYSTEM_ID policy value, the ObjectId argument you pass to create_reference_with_id must be one that was previously generated by that POA; otherwise, the BAD_PARAM system exception may be (but need not be) raised. Because of the potential for error and because ORBs are not required to detect the error, we recommend that portable applications avoid invoking create_reference_with_id on POAs with the SYSTEM_ID policy value.
A perfect use of the create_reference_with_id is to implement our CCS::Controller::list operation. This operation returns a sequence containing object references for all our Thermometer devices (including Thermostat devices, which are also Thermometer devices). Following are the relevant parts of the
Controller interface.
#pragma "acme.com"
module CCS {
interface Controller {
typedef sequence<Thermometer> ThermometerSeq; ThermometerSeq list();
// ...
402

IT-SC book: Advanced CORBA® Programming with C++
};
};
If we assume that our climate control system runs a small office building, it is likely that it controls a few hundred to a thousand devices. This means that we would waste time and application resources if we implemented list by creating a servant to represent each of these devices just to get an object reference from it. Instead, we use create_reference_with_id to create the necessary object references without creating servants.
CCS::Controller::ThermometerSeq* Controller_impl::list() throw(CORBA::SystemException)
{
//Create our return value. CCS::Controller::ThermometerSeq_var return_seq =
new CCS::Controller::ThermometerSeq(m_assets.size()); return_seq->length(m_assets.size());
//Iterate over our STL set of device asset numbers.
//The m_assets variable is our set data member. CORBA::ULong index = 0;
AssetSet::iterator iter;
for (iter = m_assets.begin(); iter != m_assets.end(); iter++)
{
CCS::AssetType anum = *iter;
//Convert asset number to a string. ostrstream ostr;
ostr < anum < ends; char * str = ostr.str();
PortableServer::ObjectId_var oid = PortableServer::string_to_ObjectId(str);
ostr.rdbuf()->freeze(0);
//Check the model type of the device so
//we can determine the right repository ID
//for the new object.
const char * repos_id; char model[32];
int ok = ICP_get(anum, "model", model, sizeof(model)); assert(ok == 0);
if (strcmp(model, "Sens-A-Temp") == 0)
repos_id = "IDL:acme.com/CCS/Thermometer: 1.0"; else
repos_id = "IDL:acme.com/CCS/Thermostat:1.0";
//Assume we already have a valid POA reference. CORBA::Object_var obj =
poa->create_reference_with_id(oid, repos_id);
//Narrow and store in our return sequence.
return_seq[index++] = CCS::Thermometer::_narrow(obj);
}
return return_seq._retn();
403

IT-SC book: Advanced CORBA® Programming with C++
}
A difference between the Controller_impl class we show in Section 10.11.2 and this example is that we use an STL set, and not a map, as the m_assets data member. This is because the Active Object Map of our POA stores the Thermometer_impl * servant pointers, so we have no need to keep a separate map that duplicates that storage. Instead, m_assets stores only asset numbers.
The list implementation iterates over the set of asset numbers. For each asset number, we must create an object reference for the device it represents. To do this, we first convert the asset number to a string using an ostrstream, and then we convert the resulting string to an ObjectId using the string_to_ObjectId helper function supplied in the PortableServer namespace (this is a stand-alone function that converts strings to ObjectIds). In addition to the ObjectId, we need the repository ID for the most derived interface our new object will support. To determine the correct repository ID, we use the asset number to read the model type directly from the device using the ICP_get device access function. If the model type indicates that the device is a thermometer, we make the repos_id variable point to the repository ID for the Thermometer interface; otherwise, we make it point to the ID for the Thermostat interface. We pass the oid and the repos_id arguments to create_reference_with_id to create the object reference for the device. Because the return value of create_reference_with_id is a reference of type CORBA::Object, though, we narrow it to the CCS::Thermometer type before we assign it to our return sequence. After we finish iterating over the m_assets set, we return the sequence of Thermometer references.
This implementation of list certainly works as desired. Moreover, the fact that we do not need to create any servants to return object references for all our devices clearly illustrates that the life cycle of a CORBA object is completely independent of the life cycle of any servant used to incarnate it. However, our list implementation contains a few assumptions that have some interesting side effects. These side effects are related to the interface types of the objects and to the eventual activation of the objects.
Narrowing Issues
Our goal in implementing list in this fashion is to avoid creating a servant for each object. Creating all those servants might be a waste of time and memory resources, mainly because it is unlikely that the client that invoked list will also invoke operations on every Thermometer that list returns.
Unfortunately, the fact that we must narrow each newly created object reference to the CCS::Thermometer interface to assign it to our sequence might mean that a servant for each object gets created anyway. This is because _narrow must verify that the object actually supports the interface being narrowed to. Most ORBs perform narrowing
404

IT-SC book: Advanced CORBA® Programming with C++
by passing the repository ID in the object reference for the target object to that object's CORBA::Object::is_a operation. Although some operations on CORBA::Object are carried out entirely on the client side, the is_a operation is not among them; usually, it is invoked directly on the target object as if it were an ordinary operation. This is why the ServantBase class provides a default implementation for _is_a, as shown in Section 11.6. To carry out the is_a request, the POA must activate the target object, meaning that the application must create and supply a servant for it. If the application must supply a servant immediately when a CORBA object is created, the optimizations afforded by create_reference and create_reference_with_id are entirely negated.
Fortunately, some ORBs use the following techniques to avoid contacting the target object to complete a_narrow request.
Because _narrow functions are provided by the static stubs compiled into the client application, some ORBs first try to down-cast through the C++ stub class hierarchy to try to find the desired interface. If the down-cast succeeds, the _narrow succeeds. Therefore, no remote message is sent, and the target object need not be activated.
Some ORBs try to match the repository ID embedded in the object reference of the target object against the repository ID of the interface being narrowed to. If they match, the ORB performs the _narrow completely within the client, and activation of the target object is not required.
Some ORBs use the Interface Repository to determine whether a narrow operation should succeed. The client ORB looks up the interface hierarchy supported by the target object by invoking operations on the Interface Repository and then comparing repository IDs to locate a match. If a match is found, the client-side _narrow succeeds and no requests are sent to the target object.
This approach, however, is generally being abandoned in favor of the other two approaches for reasons of performance. Often, relying on the Interface Repository in this manner creates both a bottleneck and a single point of failure for the entire distributed system.
To take advantage of ORBs that provide at least the first narrowing optimization, we might need to change our list implementation to narrow to the most derived interface of the object.
if (strcmp(model, "Sens-A-Temp") == 0) return_seq[i] = CCS::Thermometer::_narrow(obj);
else
return_seq[i] = CCS::Thermostat::_narrow(obj);
If the model type of the object indicates that it is a Thermometer, we narrow it to the Thermometer interface; otherwise, we narrow it to a Thermostat. If your ORB does
405

IT-SC book: Advanced CORBA® Programming with C++
not appear to optimize narrowing to avoid unnecessary object activation, you might try rewriting your code as shown to always narrow to the most derived interface, if possible. Finally, another alternative that will be available in a future version of the CORBA specification is the unchecked narrow. Such a narrow behaves exactly as its name implies: it simply assumes that the target object supports the interface in question and returns a reference of the desired type. It thus delays type checking until your first invocation of an operation using the narrowed object reference. If the object does not actually support the operation being invoked, the client will receive the standard
CORBA::BAD_OPERATION system exception.
The unchecked narrow feature was introduced by the CORBA Messaging Specification [20], which adds asynchronous messaging capabilities to CORBA. The unchecked narrow is necessary to allow objects to be invoked statically using asynchronous or store- and-forward mechanisms. Requiring synchronous narrow operations on such objects would have negated the benefits provided by asynchronous invocations on those objects.
Activation Issues
Our implementation of the list operation creates all the object references using the same POA. Unfortunately, this seemingly inconsequential choice may severely limit our activation options. Because we have not yet described explicit object activation, servant managers, or default servants, we will revisit this issue and discuss it further in each of the following sections.
We know, however, that our Controller object cannot be registered in the same POA as our Thermometer and Thermostat objects. One reason is that we use asset numbers for our object IDs, and our controller does not have an asset number; it is purely a singleton object, and, unlike the other objects in the system, it does not have a physical device counterpart. We could probably make up a special asset number for the
Controller that would not clash with any Thermometer or Thermostat asset numbers, but this approach is unnatural and is just a maintenance nightmare waiting to happen. Instead, we create a POA just for our Controller and explicitly activate a servant for it there. The following example shows how two POAs—one for Thermometer objects and one for Thermostat objects—can be created as children of a POA for the Controller.
// Initialize the ORB.
CORBA::ORB_var orb = CORBA::ORB_init(argc, argv);
//Get a reference to the Root POA. CORBA::Object_var obj =
orb->resolve_initial_references("RootPOA"); PortableServer::POA_var root_poa =
PortableServer::POA::_narrow(obj); assert(!CORBA::is_nil(root_poa));
//Create PolicyList for child POAs (not shown). CORBA::PolicyList policy_list;
406

IT-SC book: Advanced CORBA® Programming with C++
// Invoke create_POA to create the Controller child POA. PortableServer::POA_var controller_poa =
root_poa->create_POA("controller", PortableServer::POAManager::
_nil(),
policy_list);
//Now create Thermometer and Thermostat POAs as children
//of the Controller POA.
PortableServer::POA_var thermometer_poa = controller_poa->create_POA("thermometer",
PortableServer::POAManager::_nil(), policy_list);
PortableServer::POA_var thermostat_poa = controller_poa->create_POA("thermostat",
PortableServer::POAManager::_nil(), policy_list);
Creating our POAs in this order can be very helpful at server shutdown time because it ensures that all the Thermometer servants and Thermostat servants are etherealized before the Controller servant. This is because child POAs are destroyed before their parent POAs, and, if the child has a ServantActivator, its servants will be etherealized before the parent's servants. We modified our Controller_impl to keep track only of asset numbers rather than also keeping track of the Thermometer and Thermostat servants, so this POA hierarchy is no longer as important as it would be for the CCS implementation we show in Chapter 10. However, creating our hierarchy in this manner allows us to experiment with the relationships between our Controller, Thermometer, and Thermostat servants without also having to continually modify the POA hierarchy. We provide more details concerning application shutdown and servant etherealization in Section 11.13.
11.7.2 Servant Registration
One of the most straightforward ways to activate an object is to use the POA object activation operations. With these operations, the application developer explicitly supplies a servant to incarnate the object being activated, and, depending on the POA's IdAssignmentPolicy, either the POA assigns an ObjectId or the application developer supplies one. The two activation operations are defined as follows.
module PortableServer {
exception ServantAlreadyActive {}; exception ObjectAlreadyActive {}; exception WrongPolicy {};
interface POA {
ObjectId activate_object(in Servant p_servant) raises(ServantAlreadyActive, WrongPolicy);
407

IT-SC book: Advanced CORBA® Programming with C++
void |
activate_object_with_id( |
|
in ObjectId id, in Servant p_servant |
|
) raises( |
|
ServantAlreadyActive, |
|
ObjectAlreadyActive, |
|
WrongPolicy |
|
); |
}; |
// ... |
|
|
// ... |
|
};
You choose either activate_object or activate_object_with_id based on the policies of the target POA.
The activate_object operation requires the target POA to have an
IdAssignmentPolicy value of SYSTEM_ID and a ServantRetentionPolicy value of RETAIN. If either of these policies does not have the required value, activate_object raises the WrongPolicy exception.
The activate_object_with_id operation requires the target POA to have a ServantRetentionPolicy value of RETAIN. If the POA does not have the
RETAIN value for this policy, activate_object_with_id raises the WrongPolicy exception.
If the IdUniquenessPolicy of the POA is set to UNIQUE_ID and if the Servant passed as an argument is already in the POA's Active Object Map, both activate_object and activate_object_with_id raise the
ServantAlreadyActive exception. Because C++ servants are passed into POA operations as ServantBase *, the POA uses pointer comparison to check to see whether a given C++ servant is already in its Active Object Map.
The following example shows how you would create a servant for the Controller interface for the climate control system. First, we define the Controller_impl class.
#include <set> #include "CCSS.hh"
class Controller_impl : public virtual POA_CCS::Controller
{
public:
// CORBA operations.
virtual CCS::Controller::ThermometerSeq *
list() throw(CORBA::SystemException);
virtual void
find(CCS::Controller::SearchSeq & slist) throw(CORBA::SystemException);
virtual void change(
const CCS::Controller::Thermostat Seq & tlist, CORBA::Short delta
408

IT-SC book: Advanced CORBA® Programming with C++
) throw( CORBA::SystemException, CCS::Controller::EChange
);
//Constructor and destructor. Controller_impl();
virtual ~Controller_impl();
//Helper functions to allow thermometers and
//thermostats to add themselves to the m_assets set
//and to remove themselves again.
void add_impl(CCS::AssetType anum); void remove_impl(CCS::AssetType anum);
CORBA::Boolean exists(CCS::AssetType anum) const;
private:
//Set type for storing known devices. typedef set<CCS::AssetType> AssetSet;
//Set of known devices.
AssetSet m_assets;
// copy not supported Controller_impl(const Controller_impl &); void operator=(const Controller_impl &);
// Helper class for find() operation not shown.
};
This class definition is a little different from the one we show in Section 10.11.1. Our class uses an STL set type as the m_assets data member to hold all known device asset numbers. When created, a Controller_impl instance fills its m_assets set by reading device asset numbers from a file (not shown).
Next, we use the Controller_impl servant class to activate a Controller object.
//Create our Controller servant. Controller_impl ctrl_servant;
//Create our Controller ObjectId. PortableServer::ObjectId_var oid =
PortableServer::string_to_ObjectId("Controller");
//Activate our Controller.
poa->activate_object_with_id(oid, &ctrl_servant);
First, we create the Controller servant. We assume that we are creating the Controller_impl instance directly on the stack in our main function, so there is no danger of its going out of scope while the POA is still trying to dispatch to it. Next, we create the ObjectId by using the string_to_ObjectId helper function to convert
409

IT-SC book: Advanced CORBA® Programming with C++
the string "Controller" into an ObjectId. Finally, we pass our servant and its
ObjectId to activate_object_with_id to activate the Controller object.
Figure 11.7 illustrates the entry we create in the POA's Active Object Map with the invocation of activate_object_with_id.
Figure 11.7 Active Object Map entry for the Controller.
Our new Active Object Map entry is logically a key-value pair, with the key set to the object identifier "Controller" (converted to a sequence of octet) and the value set to the address of the Controller_impl servant.
Despite their names, these activation operations are also capable of creating CORBA objects under the right circumstances. For example, invoking activate_object on a POA supporting the SYSTEM_ID, TRANSIENT, and RETAIN policy values creates a new transient object if the servant passed to it is not already in the POA's Active Object Map. Similarly, invoking activate_object_with_id on a POA with the USER_ID and RETAIN policy values also creates a new object if the servant passed to it is not already in the POA's Active Object Map. As we describe in Section 11.7.1, when creating an object, the POA requires the repository ID of the most derived interface that the new object will support. When activate_object or activate_object_with_id is used to create an object, the POA gets the repository ID for the new object from the skeleton in a way that is private to each ORB implementation.
When you invoke activate_object or activate_object_with_id, the POA invokes _add_ref on the servant you pass in. The POA does this because it needs to make sure that nobody deletes the servant while it has a pointer to it stored in its Active Object Map. When the POA no longer needs the servant, it invokes _remove_ref to drop the servant's reference count.
410

IT-SC book: Advanced CORBA® Programming with C++
There is one small problem with using the activation operations to create objects: neither activate_object nor activate_object_with_id returns the object reference of the new object. One way to get the object reference of the newly activated object is to invoke the id_to_reference operation on the POA after invoking either activate_object or activate_object_with_id. The following example assumes that we have already created a Controller_impl servant as in the preceding example.
//Activate Controller object for SYSTEM_ID POA. PortableServer::ObjectId_var oid =
poa->activate_object(ctrl_servant);
//Obtain the object reference for the ObjectId. CORBA::Object_var object = poa->id_to_reference(oid);
//Narrow to the thermometer interface. CCS::Controller_var controller =
CCS::Controller::_narrow(object);
Like activate_object and activate_object_with_id, the id_to_reference operation requires the POA to have the RETAIN policy value; otherwise, it raises the WrongPolicy exception. Other POA conversion functions between object IDs, servants, and references are described in Section 11.8.
An even easier way to obtain the object reference for the Controller after we activate it with activate_object_with_id is to invoke _this on the Controller_impl servant.
//Create our Controller servant. Controller_impl ctrl_servant;
//Create our Controller ObjectId. PortableServer::ObjectId_var oid =
PortableServer::string_to_ObjectId("Controller");
//Activate our Controller.
poa->activate_object_with_id(oid, &ctrl_servant);
// Obtain the Controller object reference. CCS::Controller_var ctrl = ctrl_servant._this();
Because we have already registered our Controller_impl instance to incarnate our Controller object, invoking _this on it merely returns the existing Controller object reference. This implies that _this is a multipurpose function. As you first saw in Chapter 9, invoking _this under the right circumstances can implicitly create and activate a CORBA object. Here, with the object already activated, it just returns the object reference. If our Controller_impl servant is registered on any POA other than the Root POA, we must also override the _default_POA function inherited from
411

IT-SC book: Advanced CORBA® Programming with C++
ServantBase to return that POA, otherwise _this will erroneously register our servant with the Root POA.
The Controller is a perfect candidate for explicit activation because it is a singleton object. Because only a single instance ever exists, defining and implementing a Controller servant manager for it is overkill. Furthermore, the Controller is key to the operation of our CCS because it provides access to all the devices in the system. This means that it will be invoked almost immediately when the system starts operating, which means that it will immediately require a servant.
Because it supplies access to all system devices, the Controller serves as the "entry point" into our climate control system. We therefore need it to be a persistent object so that our clients can continue using their Controller object references even if we take the system down for maintenance and then restart it. If our Controller object is to be persistent, it must be created within a POA that has the PERSISTENT value for the LifespanPolicy. This implies that we must create a POA for our controller because the Root POA does not support persistent objects.
//Create a PERSISTENT LifespanPolicy object. PortableServer::LifespanPolicy_var lifespan =
root_poa->create_lifespan_policy(PortableServer::PERSISTENT);
//Create a USER_ID IdAssignmentPolicy object. PortableServer::IdAssignmentPolicy_var assign =
root_poa->create_id_assignment_policy(PortableServer::USER_ID);
//Create PolicyList.
CORBA::PolicyList policy_list; policy_list.length(2); policy_list[0] =
PortableServer::LifespanPolicy::_duplicate(lifespan); policy_list[1] =
PortableServer::IdAssignmentPolicy::_duplicate(assign);
//Create the child POA. PortableServer::POA_var ctrl_poa =
root_poa->create_POA("Controller", nil_mgr, policy_list);
//Create our Controller servant.
Controller_impl ctrl_servant;
//Create our Controller ObjectId. PortableServer::ObjectId_var oid =
PortableServer::string_to_ObjectId("Controller");
//Activate our Controller.
ctrl_poa->activate_object_with_id(oid, &ctrl_servant); // Destroy our policy objects.
lifespan->destroy(); assign->destroy();
The two policies we must create for our Controller POA are the life span policy (so that we can specify the PERSISTENT value) and the ID assignment policy (so that we can specify the USER_ID value). The defaults for all the other policy types suffice. We then use these policies to create our Controller POA as a child of the Root POA. After that, we create our servant and explicitly register it with our new POA as before. Finally, we
412

IT-SC book: Advanced CORBA® Programming with C++
destroy our policy objects because we no longer need them. Note that even though we stored the policy object references in instances of the LifespanPolicy_var and IdAssignmentPolicy_var types for automatic cleanup, we must still explicitly invoke destroy to get rid of them. This is because the _var objects clean up only the object references and not the policy objects themselves.
11.7.3 Servant Managers
For some applications, explicit servant registration is prohibitively expensive; for others, it is virtually impossible. These types of applications might contain many thousands of objects, and creating and registering a servant for each one could require too much memory or require too many costly database lookups. Alternatively, an application might function as a gateway to another distributed system and thus might have to learn of the presence of objects dynamically. When a new object is created in the foreign system, the gateway must instantiate a servant for it on-the-fly. In both cases, these applications would prefer to activate objects on demand as requests are actually made on them rather than having to activate them all before the ORB starts listening for requests.
A POA that has the USE_SERVANT_MANAGER policy value supports these types of applications by allowing them to create servant managers, which actively participate in the process of determining object-to-servant associations. A servant manager is a callback object that the application registers with a POA to assist or even replace the function of the POA's own Active Object Map. When the POA attempts to determine the servant associated with a given target object, it calls back to the application's servant manager to obtain the servant.
There are two types of servant managers.
For a POA with the RETAIN value for the ServantRetention policy, your servant manager object must support the ServantActivator interface.
For a POA with the NON_RETAIN policy value, your servant manager must support the
ServantLocator interface.
Before we describe these interfaces in detail, we must first show some IDL definitions that support the ServantActivator and ServantLocator interfaces:
module PortableServer { exception ForwardRequest {
Object forward_reference;
};
interface ServantManager {};
// ...
};
413

IT-SC book: Advanced CORBA® Programming with C++
The ForwardRequest exception can be raised by implementations of ServantActivator and ServantLocator to signify that the request should be processed by a different object, perhaps in another server. For interoperable applications using IIOP to communicate with remote clients and objects, the ORB turns the ForwardRequest exception into a LOCATION_FORWARD reply status, which directs the requesting ORB to redirect the request to the object denoted by the forward_reference member of the exception. See Chapter 13 for more details concerning LOCATION_FORWARD.
The ServantManager interface serves as a base interface for both the ServantActivator and ServantLocator interfaces. Its primary purpose is to allow objects that support either of these interfaces to be registered with the POA and to be managed using a single set of operations.
Servant Activators
The ServantActivator interface supplies the incarnate and etherealize operations:
module PortableServer {
interface ServantActivator : ServantManager {
Servant |
incarnate( |
|
|
in ObjectId oid, |
|
|
in POA |
adapter |
|
) raises(ForwardRequest); |
|
void |
etherealize( |
|
|
in ObjectId oid, |
|
|
in POA |
adapter, |
|
in Servant |
serv, |
|
in boolean |
cleanup_in_progress, |
|
in boolean |
remaining_activations |
|
); |
|
};
// ...
};
When a POA with the RETAIN policy value receives a request for a target object, it consults its Active Object Map to see whether a servant is already available for that object. If none is found but the application has registered a ServantActivator with the POA, the POA invokes the incarnate operation of the ServantActivator object, passing it both the ObjectId of the target object and a reference to itself. The implementation of the incarnate operation either creates a suitable instance of a servant and returns it, raises a system exception, or raises a ForwardRequest exception.
414

IT-SC book: Advanced CORBA® Programming with C++
Because a ServantActivator is itself an object, you must create and activate it before it can be registered with a POA. Here is an example definition of a
ServantActivator servant class:
#include <poaS.hh>
class Controller_impl;
class ThermometerActivator_impl :
public virtual POA_PortableServer::ServantActivator { public:
ThermometerActivator_impl(Controller_impl & ctrl); virtual ~ThermometerActivator_impl() {}
virtual PortableServer::Servant incarnate(
const PortableServer::ObjectId & oid, PortableServer::POA_ptr poa
) throw(
CORBA::SystemException, PortableServer::ForwardRequest
);
virtual void etherealize(
const PortableServer::ObjectId & oid,
PortableServer::POA_ptr |
poa, |
PortableServer::Servant |
serv, |
CORBA::Boolean |
cleanup_in_progress, |
CORBA::Boolean |
remaining_activations |
) throw(CORBA::SystemException); |
|
private: |
|
Controller_impl & m_ctrl; |
|
// copy not supported ThermometerActivator_impl(
const ThermometerActivator_impl & t
);
void operator=(const ThermometerActivator_impl &t);
};
Like any servant class, our ThermometerActivator_impl class derives from its skeleton class, which in this case is the ServantActivator skeleton in the POA_PortableServer namespace. It overrides the pure virtual functions it inherits, which represent the operations on the ServantActivator IDL interface.
Our implementation of the incarnate function must check to see that the device that the target object corresponds to actually exists. It does this by invoking the public helper function exists on the Controller_impl. The implementation of Controller_impl::exists simply checks for the device asset number in the set of known assets. This is necessary because our ICP network does not allow direct probes for devices.
415

IT-SC book: Advanced CORBA® Programming with C++
CORBA::Boolean Controller_impl::exists(CCS::AssetType anum) const
{
return m_assets.find(anum) != m_assets.end();
}
If we find the device number in the m_assets set, exists returns true; otherwise, it returns false.
Our servant activator implementation assumes that object IDs are strings containing the asset numbers of our devices. We first attempt to convert the oid argument from an ObjectId to a string using the ObjectId_to_string helper function supplied in the PortableServer namespace. This function throws a CORBA::BAD_PARAM exception if the object ID contains any octet values that are illegal string characters. Because we know that our object IDs contain only printable characters, we catch that exception and throw a CORBA::OBJECT_NOT_EXIST exception to indicate that the object ID does not represent any known object in this POA. Assuming that ObjectId_to_string is successful, we then parse the object ID string using an istrstream to turn the string back into an actual asset number.
PortableServer::Servant ThermometerActivator_impl:: incarnate(
const PortableServer::ObjectId & oid, PortableServer::POA_ptr poa
) throw(CORBA::SystemException, PortableServer::ForwardRequest)
{
//Check to see if the object ID is valid. CORBA::String_var oid_string;
try {
oid_string = PortableServer::ObjectId_to_string(oid); } catch(const CORBA::BAD_PARAM&) {
throw CORBA::OBJECT_NOT_EXIST();
}
//Get the asset number from the oid_string.
istrstream istr(oid_string.in()); CCS::AssetType anum;
istr >> anum; if (istr.fail())
throw CORBA::OBJECT_NOT_EXIST();
//Does the object ID denote one of our assets? if (!m_ctrl.exists(anum))
throw CORBA::OBJECT_NOT_EXIST();
//Get the model identifier from the device. PortableServer::Servant servant = 0;
char model[32];
if (ICP_get(anum, "model", model, sizeof(model)) != 0) abort();
416

IT-SC book: Advanced CORBA® Programming with C++
if (strcmp(model, "Sens-A-Temp") == 0) servant = new Thermometer_impl(anum);
else
servant = new Thermostat_impl(anum); return servant;
}
Next, we invoke Controller_impl::exists as described earlier. If it returns true, we use the ICP network to determine the model type of the device. Depending on the model type, we create either a Thermometer_impl servant or a Thermostat_impl servant. Either way, the servant is created on the heap because the POA, which must have the RETAIN policy value for servant activators to work, will keep a pointer to it in its Active Object Map. We can eventually invoke delete on the servant when the etherealize function is invoked.
The etherealize function, which allows applications to clean up their servants, is the opposite of the incarnate function. The POA normally invokes etherealize in response to an explicit object deactivation via deactivate_object (even if the servant for that object was not created by the servant activator) or in response to the deactivation or destruction of the POA itself. Our implementation of etherealize is very simple, only checking to make sure that the servant is no longer in use before invoking delete on it.
void ThermometerActivator_impl:: etherealize(
const PortableServer::ObjectId & oid,
PortableServer::POA_ptr |
poa, |
PortableServer::Servant |
servant, |
CORBA::Boolean |
cleanup_in_progress, |
CORBA::Boolean |
remaining_activations |
) throw(CORBA::SystemException) |
|
{ |
|
if (!remaining_activations) |
|
delete servant; |
|
} |
|
Alternatively, if our servant uses actual reference counting (such as that provided by the RefCountServantBase mix-in class) and invokes delete on itself when its reference count drops to zero, we can make etherealize call _remove_ref on the servant instead of directly invoking delete.
Before the POA calls the etherealize function, it removes the Active Object Map entry corresponding to the target object. Because a servant can incarnate multiple CORBA objects simultaneously, the remaining_activations argument is true (non-zero) if the servant still incarnates other objects and thus is still present in other Active Object Map entries. If remaining_activations is false (zero), we do not drop the servant's reference count; in this way, we keep the servant available for the other
417

IT-SC book: Advanced CORBA® Programming with C++
Active Object Map entries. The cleanup_in_progress argument, which we do not use in our example, is true if the POA invoked etherealize in response to its own imminent deactivation or destruction. Applications that want to perform additional servant housekeeping chores when the POA is being shut down can use the cleanup_in_progress flag as an indication of when to do so. After etherealize returns, the POA does not access the servant in any way because etherealize may have destroyed it.
For multithreaded systems, the POA makes certain guarantees concerning invocations of the incarnate and etherealize functions. These guarantees prevent your ServantActivator from creating duplicate servants for the same object ID simultaneously in multiple threads, and they also prevent it from simultaneously etherealizing the same servant in multiple threads.
A POA never simultaneously invokes incarnate or etherealize on a given ServantActivator for the same object ID from multiple threads.
For a given object ID, a POA never invokes incarnate on a given ServantActivator while it is already in the process of carrying out an invocation of its etherealize function, or vice versa. In other words, a POA will never cause incarnate and etherealize to execute simultaneously on a single ServantActivator for the same object ID.
If an object is deactivated directly, the POA queues new requests for it until etherealize completes. If etherealize is called for an object as a result of the deactivation of the POA, all new requests for that object are rejected (see Section 11.7.6).
Note that if you use the same ServantActivator in multiple POAs, the POAs will not interact to uphold these guarantees. If for some reason you want to use the same ServantActivator with multiple POAs, make sure that your implementation is thread-safe. For maximal portability, we recommend using a ServantActivator for only a single POA at a time.
Servant Locators
For POAs with the USE_SERVANT_MANAGER and NON_RETAIN policy values, your servant manager must support the ServantLocator interface. The ServantLocator interface provides the preinvoke and postinvoke operations:
module PortableServer {
interface ServantLocator : ServantManager {
native Cookie; |
|
|
Servant |
preinvoke( |
oid, |
|
in ObjectId |
418

IT-SC book: Advanced CORBA® Programming with C++
|
in POA |
adapter, |
|
in CORBA::Identifier |
operation, |
|
out Cookie |
the_cookie |
|
) raises(ForwardRequest); |
|
void |
postinvoke( |
oid, |
|
in ObjectId |
|
|
in POA |
adapter, |
|
in CORBA::Identifier |
operation, |
|
in Cookie |
the_cookie, |
|
in Servant |
serv |
}; |
); |
|
|
|
|
// ... |
|
|
};
A POA with the NON_RETAIN policy value does not store object-to-servant associations in its Active Object Map, so it must invoke its ServantLocator for each incoming request. It first invokes preinvoke to obtain a servant to dispatch the request to. After the request returns, the POA invokes postinvoke to allow the ServantLocator to perform servant cleanup or other post-invocation functions. As far as the POA is concerned, the servant returned by preinvoke is used only for a single request.
Defined within the ServantLocator interface is the Cookie type, another native IDL type. The Cookie IDL type, which maps to void * in C++, allows the ServantLocator to associate an invocation of preinvoke with its matching postinvoke call. Being a void *, the Cookie can carry whatever state the ServantLocator implementation needs for servant instantiation and cleanup. The POA simply passes the Cookie along without interpreting it.
The POA passes the ObjectId of the target object, a reference to itself, and the name of the operation being invoked to the preinvoke operation. The implementation of the preinvoke operation either returns a servant to carry out the request, raises a system exception, or raises a FormalRequest exception. In addition, it can set the Cookie output parameter to a value that the POA will pass back to it in the postinvoke call after the request completes. If the implementation of the ServantLocator does not need the Cookie parameter, it need not use it.
We can define a ServantLocator servant class similar to the one we defined earlier for the ServantActivator interface:
#include <poaS.hh>
class Controller_impl;
class ThermometerLocator_impl :
public virtual POA_PortableServer::ServantLocator
{
419

IT-SC book: Advanced CORBA® Programming with C++
public:
ThermometerLocator_impl(Controller_impl & ctrl); virtual ~ThermometerLocator_impl() {}
virtual PortableServer::Servant preinvoke(
const PortableServer::ObjectId & oid,
PortableServer::POA_ptr |
poa, |
const char * |
operation, |
void * & |
cookie |
) throw( |
|
CORBA::SystemException, PortableServer::ForwardRequest
);
virtual void postinvoke(
const PortableServer::ObjectId & oid,
PortableServer::POA_ptr |
poa, |
const char * |
operation, |
void * |
cookie, |
PortableServer::Servant |
servant |
) throw(CORBA::SystemException); |
|
private:
Controller_impl & m_ctrl; // copy not supported
ThermometerLocator_impl(const ThermometerLocator_impl & t); void operator=(const ThermometerLocator_impl & t);
};
The constructor initializes the m_ctrl data member to refer to the singleton
Controller_impl servant. As with the ThermometerActivator_impl shown in the preceding section, ThermometerLocator_impl uses the Controller_impl::exists function to make sure that the target device still exists before creating a servant for it.
The preinvoke function first checks that the object ID can be converted to a string, and then it checks the contents of the resulting string by attempting to read an asset number from it. If either of these fails, we throw the CORBA::OBJECT_NOT_EXIST exception to indicate that the object ID is not valid for any object in this POA.
PortableServer::Servant
ThermometerLocator_impl:: preinvoke(
const PortableServer::ObjectId & oid,
PortableServer::POA_ptr |
poa, |
const char * |
operation, |
void * & |
cookie |
) throw(CORBA::SystemException, PortableServer::ForwardRequest)
{
// Check to see if the object ID is valid. CORBA::String_var oid_str;
try {
420

IT-SC book: Advanced CORBA® Programming with C++
oid_str = PortableServer::ObjectId_to_string(oid); } catch(const CORBA::BAD_PARAM &) {
throw CORBA::OBJECT_NOT_EXIST();
}
//Get the asset number from the oid_string. istrstream istr(oid_str.in()); CCS::AssetType anum;
istr >> anum; if (istr.fail())
throw CORBA::OBJECT_NOT_EXIST();
//Does the object ID denote one of our assets? if (!m_ctrl.exists(anum))
throw CORBA::OBJECT_NOT_EXIST();
//Get the model identifier from the device. PortableServer::Servant servant = 0;
char model[32];
if (ICP_get(anum, "model", model, sizeof(model)) != 0) abort();
if (strcmp(model, "Sens-A-Temp") == 0) servant = new Thermometer_impl(anum);
else
servant = new Thermostat_impl(anum); return servant;
}
The preinvoke implementation is identical to the
ThermometerActivator_impl::incarnate function in the preceding section. It checks the asset number with the Controller_impl to make sure the device is valid, reads the model type from the device over the ICP network, and returns a servant of the appropriate type.
Note that preinvoke receives some arguments that our example does not use. In addition to the Cookie parameter, it receives a reference to the POA that invoked it and a string indicating the name of the operation that will be invoked on the returned servant. The operation name can be especially useful if you want your ServantLocator to return a different servant depending on which operation is being invoked.
Our implementation of postinvoke simply invokes delete on the servant. Alternatively, if our servant uses reference counting so that it invokes delete on itself when its reference count drops to zero (perhaps by inheriting its _remove_ref implementation from the RefCountServantBase mix-in class), postinvoke can call _remove_ref on the servant rather than directly invoke delete.
void ThermometerLocator_impl:: postinvoke(
const PortableServer::ObjectId & /* oid |
*/, |
|
PortableServer::POA_ptr |
/* |
poa */, |
421

IT-SC book: Advanced CORBA® Programming with C++
const char * |
/* operation */, |
void * |
/* cookie */, |
PortableServer::Servant |
servant |
) throw(CORBA::SystemException) |
|
{ |
|
delete servant; |
|
} |
|
Unlike ThermomemeterActivator::etherealize, the postinvoke function does not have to worry about whether the servant is still in use for other request invocations. Because the POA has the NON_RETAIN policy value, it has no Active Object Map to keep track of servants. This means that the servant that our ThermometerLocator_impl returns from preinvoke is used only for the request that caused the POA to call preinvoke.
For portability across multithreaded environments, the POA makes certain guarantees concerning invocations of preinvoke and postinvoke.
The request that causes the POA to invoke preinvoke is the only request that the POA will process using the servant returned by postinvoke. After the request completes, the POA will pass the servant to postinvoke.
For a given request, the invocation of preinvoke, the processing of the request, and the invocation of postinvoke all occur in the same thread.
An ORB_CTRL_MODEL POA that uses multiple threads does not prevent concurrent invocations of preinvoke or postinvoke on a single ServantLocator for the same object ID. This means that a ServantLocator can cause a single CORBA object to be incarnated by more than one servant simultaneously if preinvoke is upcalled concurrently from multiple threads.
Servant Manager Registration
Because servant managers are themselves CORBA objects, you need object references for them to register them with a POA. The easiest way to create an object reference for a servant manager is to implicitly register its servant in the Root POA:
//Create our Controller servant. Controller_impl ctrl_servant;
//Create a ThermometerActivator servant. ThermometerActivator_impl manager_impl(ctrl_servant);
//Create a new transient servant manager object
//in the Root POA.
PortableServer::ServantManager_var mgr_ref = manager_impl._this();
//Set the servant manager for another POA. Because we
//are registering a ServantActivator, we assume our
//POA has the RETAIN policy value.
422

IT-SC book: Advanced CORBA® Programming with C++
poa->set_servant_manager(mgr_ref);
Our example shows the creation and registration of a
ThermometerActivator_impl servant activator. The set_servant_manager function expects you to pass it an object reference for a ServantManager. This means that registration of servant activators and servant locators looks identical: both have the ServantManager as a base interface. You must make sure that you pass a reference to the right ServantManager type—either a ServantActivator or a
ServantLocator —depending on whether the POA has the RETAIN or NON_RETAIN policy value. If you pass the wrong type, the POA will raise an exception. Unlike other POA-related objects, servant managers are normal CORBA objects and are not locality-constrained. Nevertheless, servant managers must be local to the POA they are serving. If they were not local, they would not be able to create and manage Servants, which are local programming language object instances, when invoked by the POA. Because servant managers must be local objects, creating them as transient objects under the Root POA makes them easy to manage and imposes no limitations on their effectiveness or utility. Moreover, even though you might create a servant manager in this manner under the Root POA, it can be used as the servant manager for any other POA.
One good reason to create your ServantManager objects under the Root POA is to ensure smooth server shutdown. During shutdown, child POAs are destroyed before their parents, meaning that the Root POA is the last POA to be destroyed. Because a POA with a ServantActivator invokes etherealize to allow the application to clean up its servants, a ServantActivator object must remain viable for the entire time the POA you registered it with is being destroyed. In other words, you must avoid creating your ServantActivator object in the same POA that uses it or a child of that POA. The easiest way to do this is just to create it under the Root POA.
Choosing Our POA Hierarchy
The servant manager registration example we showed in the preceding section does not indicate which POA is being used for the Thermometer and Thermostat objects. The discussion of object creation without servant creation in Section 11.7.1 alludes to the fact that we must carefully choose how we allocate servants to POAs so as not to limit our options for creating objects and incarnating them with servants.
First, we must choose whether our Thermometer and Thermostat objects should be persistent or transient. Because our Controller is a persistent object, we might choose to create a new POA to make the Thermometer and Thermostat objects transient. In this case, our Controller would act as a reference factory, handing out transient Thermometer and Thermostat object references to clients that ask for them. Clients would then have to always be prepared to throw away their Thermometer and Thermostat object references and ask the Controller for new ones. A client attempting to use a reference whose lifetime had expired would receive an
423

IT-SC book: Advanced CORBA® Programming with C++
OBJECT_NOT_EXIST exception; that is somewhat strange because it would then be impossible to tell whether the actual Thermometer or Thermostat device no longer existed, or whether only the reference had become invalid. In any event, after a reference stopped working, the client would have to reinvoke the Controller::find operation to obtain a new object reference for the Thermometer or Thermostat object it was interested in. Note that this implies that only the Controller object reference, which is persistent, could be advertised in an object service such as the Naming or Trading Service. Advertising the Thermometer and Thermostat object references would not make sense because of their short lifetimes. Clients retrieving outdated references from the Naming or Trading Service would simply have to go back to the Controller to get updated references.
If we instead choose to make our Thermometer and Thermostat objects persistent, we resolve all these issues. Clients can maintain references to Thermometer and Thermostat objects without having them go stale. Because they are persistent, Thermometer and Thermostat references can usefully be advertised in the Naming and Trading Services, and this means that clients need not necessarily retrieve them only from the Controller. Given that our few hundred to a thousand Thermometer and Thermostat objects represent physical devices that remain in use for years before being removed or exchanged, it makes sense to make them persistent objects.
The next issue we must resolve is how many POAs we should use for the system. All our objects—the Controller, the Thermometers, and the Thermostats—are persistent, so they are not restricted from existing under the same POA based on LifespanPolicy. However, we might want to use a different value of the
RequestProcessingPolicy for the Controller than we do for the
Thermometer and Thermostat objects. Specifically, we want to use a servant manager for the Thermometers and Thermostats, but because we have only one Controller, we want to explicitly activate it. Furthermore, the Controller does not have an asset number, so its object identifier is in a different "name space" than our
Thermometer and Thermostat asset numbers.
We can achieve our goals using two POAs: one for the Controller and a second one for all the Thermometer and Thermostat objects. We create both POAs with the default RETAIN value for the ServantRetentionPolicy, but the Controller
POA has the USE_ACTIVE_OBJECT_MAP_ONLY policy value and the POA for the device objects has the USE_SERVANT_MANAGER value. These policies allow us to explicitly activate the single Controller object, thus giving it an entry in the POA's Active Object Map, and use a ServantActivator to activate the Thermometer and Thermostat objects on demand. We also make the POA for the Thermometer and Thermostat objects a child of the Controller POA for the same reasons we describe in Section 11.7.1.
424

IT-SC book: Advanced CORBA® Programming with C++
One potentially negative side effect of this approach is that each Thermometer or Thermostat activation results in an entry in the Active Object Map. In the worst case—in which all the Thermometer and Thermostat objects receive request invocations—the map will hold an object-to-servant association for each one. However, given that our entire CCS consists of only several hundred to a thousand objects, our server application can easily handle this worst case scenario without running out of resources. Furthermore, we could even choose to keep track of the number of servants we have activated and use POA::deactivate_object to try to explicitly remove some of them from the Active Object Map if our count exceeds a predetermined threshold. We describe deactivate_object in Section 11.9 and show extensive examples that use it in Chapter 12.
11.7.4 Default Servants
The final way that an application can register servants to incarnate CORBA objects is by using a default servant. For this approach, the POA must have the
RequestProcessingPolicy value of USE_DEFAULT_SERVANT. The POA dispatches each request to a single default servant if it has no servant in its Active Object Map for the ObjectId of the target object or if it has the NON_RETAIN value for the ServantRetentionPolicy. The default servant acts as a catch-all servant for those objects that do not have their own servants. Because each object incarnated by the default servant must support the same interface, default servants are often used for applications based on the Dynamic Skeleton Interface (DSI). Default servants can also be used if each object created within a POA supports the same interface, even if those objects are incarnated with servants based on static skeletons.
Because they incarnate multiple CORBA objects, a key aspect of default servants is that they must not hold object-specific state. Unfortunately, the servant classes we have used thus far to implement the Thermometer and Thermostat objects hold the asset numbers of their objects as data members. In other words, they assume that they incarnate only a single CORBA object. We must therefore redesign these servant classes if we are to use them for default servants.
The PortableServer::Current Interface
Within the context of a request dispatch, the server ORB allows an application to obtain the ObjectId of the target object and a reference to the POA that is dispatching the request. These operations are provided by the PortableServer:: Current interface.
module PortableServer {
interface Current : CORBA::Current {
exception NoContext {}; |
|
|
POA |
get_POA() |
raises(NoContext); |
ObjectId get_object_id() raises(NoContext);
};
425

IT-SC book: Advanced CORBA® Programming with C++
// ...
};
The Current interface derives from the empty CORBA::Current interface. CORBA::Current is the base for several Current interfaces in addition to the one in the PortableServer module; each of them allows access to information concerning the thread of control from which its operations are invoked. For example, the OMG Transaction Service (OTS) supplies its own Current interface in the CosTransactions module. The OTS Current allows applications to obtain information concerning any transaction that the calling thread may be a part of and to control that transaction by committing it or aborting it.
Current objects are generally both localityand thread-constrained. They can be passed between different threads of control, but using the same Current object reference in different threads allows each thread to access only its own threadspecific state; one thread cannot use a Current from another thread to retrieve or modify the state of that thread. Moreover, Current objects do not depend on the presence of multiple threads and are thus available even if the application is only single-threaded.
You obtain a reference to the POA Current by passing the string "POACurrent" to
ORB::resolve_initial_references.
// Obtain a reference to the ORB.
CORBA::ORB_var orb = CORBA::ORB_init(argc, argv);
//Obtain a reference to the POA Current. CORBA::Object_var obj =
orb->resolve_initial_references("POACurrent");
//Narrow the result to the PortableServer::Current interface. PortableServer::Current_var cur =
PortableServer::Current::_narrow(obj);
If either of the POA Current operations is invoked outside the context of a request dispatch, it will raise the PortableServer::NoContext exception.
Thermometer Default Servant
We must reimplement the Thermometer_impl servant to eliminate its asset number data member. We replace this data member with a member that holds a reference to a POA Current.
class Thermometer_impl : public virtual POA_CCS::Thermometer { public:
Thermometer_impl(PortableServer::Current_ptr current); virtual ~Thermometer_impl() {}
// Functions for the Thermometer attributes.
426

IT-SC book: Advanced CORBA® Programming with C++
virtual CCS::ModelType
model() throw(CORBA::SystemException);
virtual CCS::AssetType
asset_num() throw(CORBA::SystemException);
virtual CCS::TempType
temperature() throw(CORBA::SystemException);
virtual CCS::LocType
location() throw(CORBA::SystemException);
virtual void |
location(const char * loc) |
|
|
throw(CORBA::SystemException); |
|
static Controller_impl * m_ctrl; |
// My controller |
protected:
PortableServer::Current_var m_current;
//Helper function that extracts asset number from
//the target ObjectId.
CCS::AssetType get_target_asset_number() throw(CORBA::SystemException);
// Helper functions that read data from the device. static CCS::ModelType get_model(CCS::AssetType anum);
static CCS::TempType |
get_temp(CCS::AssetType anum); |
static CCS::LocType |
get_loc(CCS::AssetType anum); |
static void |
set_loc(CCS::AssetType anum, |
|
const char * new_loc); |
private:
// copy not supported for this class Thermometer_impl(const Thermometer_impl & therm); void operator=(const Thermometer_impl & therm);
};
Servants of type Thermometer_impl are constructed with a reference to the POA Current object. Next we show alternative implementations of the Thermometer_impl servant class methods, each of which uses the get_target_asset_number helper function to extract the asset number of the target object from the object ID obtained from the POA Current.
// Constructor Thermometer_impl:: Thermometer_impl(
PortableServer::Current_ptr current
) : m_current(PortableServer::Current::_duplicate(current))
{
// Intentionally empty
}
// Member functions CCS::ModelType
427

IT-SC book: Advanced CORBA® Programming with C++
Thermometer_impl:: model() throw(CORBA::SystemException)
{
CCS::AssetType anum = get_target_asset_number(); return get_model(anum);
}
CCS::AssetType
Thermometer_impl::asset_num() throw(CORBA::SystemException)
{
return get_target_asset_number();
}
CCS::TempType Thermometer_impl::
temperature() throw(CORBA::SystemException)
{
CCS::AssetType anum = get_target_asset_number(); return get_temp(anum);
}
CCS::LocType Thermometer_impl::
location() throw(CORBA::SystemException)
{
CCS::AssetType anum = get_target_asset_number(); return get_loc(anum);
}
void Thermometer_impl::
location(const char * new_loc) throw(CORBA::SystemException)
{
CCS::AssetType anum = get_target_asset_number(); set_loc(anum, new_loc);
}
CCS::AssetType Thermometer_impl::
get_target_asset_number() throw(CORBA::SystemException)
{
// Get the target ObjectId.
PortableServer::ObjectId_var oid = m_current->get_object_id();
//Check to see if the object ID is valid. CORBA::String_var asset_str;
try {
asset_str = PortableServer::ObjectId_to_string(oid); } catch(const CORBA::BAD_PARAM&) {
throw CORBA::OBJECT_NOT_EXIST();
}
//Convert the ID string into an asset number. istrstream istr(asset_str.in()); CCS::AssetType anum;
istr >> anum; if (istr.fail())
throw CORBA::OBJECT_NOT_EXIST();
return anum;
}
428

IT-SC book: Advanced CORBA® Programming with C++
The constructor duplicates the reference to the POA Current object passed to it and stores the result in the m_current data member. The get_target_asset_number helper function invokes the get_object_id operation on the m_current data member to retrieve the ObjectId of the target object, converts the ObjectId to a string, and then extracts the asset number from it. All the member functions that implement IDL operations rely on the get_target_asset_number helper function to get the asset number of the target object, which they use to access the target device over the ICP network as usual.
To set the default servant, we invoke set_servant on the POA, passing it a pointer to a Thermometer_impl instance.
// Create a default servant.
Thermometer_impl * dflt_servant = new Thermometer_impl(cur);
//Register it with the POA. poa->set_servant(dflt_servant);
//Because our servant inherits reference counting
//from RefCountServantBase, we call _remove_ref because
//we no longer need the servant.
dflt_servant->_remove_ref();
Because set_servant holds onto the pointer to our servant, it invokes _add_ref on the servant before returning. If the Thermometer_impl class inherits its reference counting implementations from the PortableServer:: RefCountServantBase mix-in class, we can invoke _remove_ref on our servant after set_servant returns. When the POA is eventually destroyed, it will invoke _remove_ref on the default servant to remove its reference count, and the servant will delete itself. If you do not choose to derive your default servant class from RefCountServantBase, you must remember to destroy your default servant instance yourself.
If you want to obtain a pointer to a POA's default servant, you can invoke get_servant.
//Use a ServantBase_var to capture the return value
//of get_servant.
PortableServer::ServantBase_var servant = poa->get_servant();
//Use dynamic_cast to get back to our original
//default servant's type.
Thermometer_impl * dflt_servant = dynamic_cast<Thermometer_impl *>(servant.in());
429

IT-SC book: Advanced CORBA® Programming with C++
Our example uses a PortableServer::ServantBase_var to store the return value of get_servant. This is because the POA invokes _add_ref on the default servant before returning it, making us responsible for eventually invoking _remove_ref on it when we are finished with it. This prevents the default servant from accidentally being deleted out from under the POA. The ServantBase_var is just like any other _var type, releasing its resources (in this case, using _remove_ref) in its destructor. Note also that servants do not provide any narrowing operations, so to regain the derived type of the default servant, we must use a dynamic_cast. If your C++ compiler does not support dynamic_cast, it is not possible to obtain the real type of a servant returned from get_servant in a portable fashion.
Scalability Using Default Servants
The scalability aspects of the default servant approach cannot be overemphasized. A default servant provides the ability to support literally an infinite number of objects in a fixed amount of memory. The servant itself is stateless, and it depends on retrieving the state from the target device itself. The trade-off, of course, is that finding the state of the target object is slower than using a separate servant per object because we must first extract the target's object identifier from the POA Current. Then we extract the asset number from the object identifier and use the ICP network to invoke the actual device to carry out the request.
Revising Our POA Hierarchy
If we revise our design to use default servants rather than servant managers, we must revisit the choices we made regarding the POA hierarchy the CCS application requires. In Section 11.7.3 we decided that we could put all the CCS objects under two POAs with the PERSISTENT value for the LifespanPolicy and the RETAIN value for the ServantRetentionPolicy. We based our design decision on the fact that we could explicitly register the single Controller object and use a ServantActivator for all the Thermometer and Thermostat objects.
The fact that a POA can have only a single default servant may tempt you to use a Thermostat servant as the default servant. After all, the Thermostat interface is derived from the Thermometer interface, meaning that a Thermostat default servant could also handle requests for Thermometer objects. Although this approach would work, the design is somewhat obscure; the hapless engineer who inherits your design and must maintain and enhance it will wonder why Thermostat servants are incarnating Thermometer objects.
A better approach is to use three POAs. As before, one POA will handle the Controller singleton object. The other two POAs will handle Thermostat objects and Thermometer objects. This arrangement allows us to use two different default servants, one for each interface type. Figure 11.8 shows the resulting POA hierarchy.
430

IT-SC book: Advanced CORBA® Programming with C++
Figure 11.8 Default servant POA hierarchy.
We create each POA with the PERSISTENT value for the LifespanPolicy. The
Controller POA has the RETAIN value for the ServantRetentionPolicy and has the USE_ACTIVE_OBJECT_MAP_ONLY value for the
RequestProcessingPolicy because we must explicitly register the Controller servant in its Active Object Map. However, we give the Thermometer and
Thermostat POAs the NON_RETAIN value for the ServantRetentionPolicy. Neither of these POAs needs an Active Object Map because we register a default servant with each one. We also create the Thermometer and Thermostat POAs with the
USE_DEFAULT_SERVANT value for the RequestProcessingPolicy so that we can register default servants with them.
Having each POA handle objects of only a single interface type is not uncommon. As the preceding discussion indicates, it is useful when default servants are being used or, more generally, when a single servant incarnates multiple CORBA objects. It is also often used in conjunction with servant managers to avoid requiring the manager to dynamically determine the right type of servant to supply when invoked by the POA.
11.7.5 Servant Memory Management
Not surprisingly, managing the lifetimes of your servant instances is necessary for the proper operation of your applications. How you manage your servant instances depends on several factors:
Whether or not your servants are allocated on the heap
Whether or not you have chosen to use reference counting for your servants
Whether you are using a servant activator, a servant locator, or no servant manager at all Whether each of your servants incarnates only a single CORBA object or multiple objects, and whether or not you use a default servant
Whether or not your servants are registered in multiple POAs simultaneously Whether your application is multithreaded or single-threaded
431

IT-SC book: Advanced CORBA® Programming with C++
In the following sections, we explore these issues in more detail.
Stack Versus Heap Allocation
In most serious applications, servants are heap-allocated. This is because these types of applications normally use sophisticated POA features such as servant managers, which are much easier to use and maintain when applications create their servants on the heap.
For example, if a servant activator or servant locator allocated its servants on the stack in its incarnate or preinvoke function, respectively, the application would very likely crash. This is because the servant activator or servant locator would return a pointer to a servant that would be destroyed as soon as the incarnate or preinvoke function returned. Having it instead return a servant that was global or static would mean that all objects for that POA incarnated via the servant manager would use the same servant. In that case, it would be more efficient to register the single servant as the default servant for that POA.
For transient objects registered in the Root POA, allocating servants on the stack in your program's main function works well. When main ends, these servants are automatically destroyed, and that is not a problem because by then your ORB and all its POAs have also shut down. Thus, there is no danger of crashing your program due to either the ORB or its POAs attempting to access your stack-allocated servants after they have been destroyed.
Servant Reference Counting
With heap-allocated servants, you must determine a point in your program when it is safe to delete them. One way to do this for a servant that incarnates only a single CORBA object is to first deactivate the object and then delete the servant. For example, you might perform the following steps within a servant method:
void SomeServant::destroy() throw(CORBA::SystemException)
{
my_poa->deactivate_object(my_object_id); delete this;
}
Assuming that the POA does not have a servant activator registered with it, it seems that this code would work perfectly. We first deactivate the object that the servant is incarnating, and then, because the servant is no longer being used, we delete it.
In reality, this code will almost certainly cause your application to crash. The problem is that if the POA has an entry for the servant in its Active Object Map, that entry remains there until all requests on the associated object have completed. After all requests complete, the POA destroys the Active Object Map entry and invokes _remove_ref on the servant to decrement its reference count. Because we invoke deactivate_object from within a method, the Active Object Map entry will remain until this method completes. This means that by deleting the servant in the method, our
432

IT-SC book: Advanced CORBA® Programming with C++
application will probably crash when the POA invokes _remove_ref on the servant after our method completes.
Even deleting a servant in this manner outside a method can cause problems. One reason is that other parts of the application might also be accessing the servant. For example, another thread might still be executing a request using the servant. Alternatively, another part of the program may have invoked either reference_to_servant or id_to_servant on the servant's POA, may have had a pointer to the same servant returned to it, and may still be using that pointer.
The way to avoid these types of problems is to derive your servant classes from the
RefCountServantBase class provided in the PortableServer namespace.
namespace PortableServer {
class RefCountServantBase : public virtual ServantBase { public:
RefCountServantBase() : m_ref_count(1) {}
virtual void _add_ref(); virtual void _remove_ref();
private:
CORBA::ULong m_ref_count; // ...
};
}
Unlike the do-nothing versions of the _add_ref and _remove_ref functions supplied by PortableServer::ServantBase, the versions provided by the RefCountServantBase class perform thread-safe reference counting for derived servant classes. The implementation of _add_ref increments the reference count, whereas _remove_ref first decrements the reference count and then calls delete on its own this pointer if the reference count has dropped to zero. Applications that derive their servant classes from RefCountServantBase must therefore do two things.
Because RefCountServantBase::_remove_ref assumes that the servant has been allocated on the heap, always be sure to heap-allocate instances of these servant types. One way to ensure that your servant instances are always heap-allocated is to make their destructors protected or private (as recommended in [16]).
Never invoke delete directly on pointers to instances of these servant types; instead, invoke _remove_ref.
Internally, the POA cannot know beforehand whether or not your servants actually use reference counting, so it must assume that they do. The POA therefore uses servant reference counting whenever it must guarantee that a servant will not be destroyed or deleted while still in use.
433

IT-SC book: Advanced CORBA® Programming with C++
You should note that whenever you use _this to implicitly create a CORBA object and activate a servant for it (see Section 11.6), the POA invokes _add_ref on the servant so that it can safely keep a pointer to it in its Active Object Map.
As we describe in Section 11.7.4, you can use a ServantBase_var to help you manage servant reference counting. The ServantBase_var class is much like any other _var type, adopting the servant you construct it with or assign to it. Later, when the ServantBase_var destructor runs, it invokes _remove_ref on the servant it adopted.
For applications of any significant size, we recommend that you derive your servants from RefCountServantBase unless you are quite sure of what you are doing. We
provide many more details concerning safe servant destruction in Chapter 12 and
Chapter 21.
Servant Managers
When you use a servant activator, you normally call delete or invoke _remove_ref on your servants in the activator's etherealize function. The POA guarantees that all requests using the servant will have finished before etherealize is called, and it also guarantees that it will not try to use the servant in any way after it passes it to etherealize. Thus, if you are not using reference counting for your servants and you delete them in etherealize, there is only one danger for error: another part of your code is holding a pointer to the servant obtained from POA:: reference_to_servant or POA::id_to_servant, and it tries to use that dangling pointer to access the nowdeleted servant.
With a servant locator, invoking POA::reference_to_servant or POA::id_to_servant is not possible because both of them require the RETAIN policy value on the target POA. The POA guarantees that it will use the servant returned from the locator's preinvoke function for only the request specified by the ObjectId and Identifier arguments passed to postinvoke. It further guarantees that it will invoke the locator's postinvoke function immediately after the servant completes its request. This strongly implies a model in which servants are allocated on the heap in preinvoke and then deleted in postinvoke, although a sophisticated servant locator implementation might instead keep a pool of servants rather than continually creating and deleting them.
If you are using a POA with the RETAIN policy value, we recommend using a servant activator in conjunction with it even if you intend to register all your objects explicitly. This is because the activator's etherealize function provides a convenient place to clean up your servants at application shutdown.
Single-Object Versus Multiobject Servants
434

IT-SC book: Advanced CORBA® Programming with C++
Managing a servant that incarnates only a single object is easy because it can be destroyed when the object it incarnates is deactivated. As noted earlier, this is especially easy when you also use a servant activator.
Knowing when to destroy servants that incarnate multiple objects is more difficult. Typically, these servants are best destroyed when the POA itself is destroyed, something that also holds true for default servants.
Multi-POA Servants
If you choose to register the same servant into multiple POAs, it is entirely up to you to ensure that its life cycle is managed properly. This is because POAs do not communicate with one another to determine whether they are sharing servants.
Threading Issues
The proper destruction of servants for applications that execute in multithreaded environments can be somewhat difficult. We discuss these issues and present an extended example in Chapter 21.
11.7.6 Request Processing
A POA dispatches requests according to the settings of several of its policies, especially the value of its RequestProcessingPolicy.
If a POA has a RequestProcessingPolicy value of
USE_ACTIVE_OBJECT_MAP_ONLY, it looks in its Active Object Map for the object ID of the target object. The POA must also have a ServantRetentionPolicy value of RETAIN in this case. If it does not find the target object ID, the POA raises the
OBJECT_NOT_EXIST exception.
A POA with a RequestProcessingPolicy value of USE_SERVANT_MANAGER and a ServantRetentionPolicy value of RETAIN searches its Active Object Map for the object ID of the target object. If it does not find it and if a ServantActivator has been registered with the POA, its incarnate operation is invoked. The implementation of ServantActivator::incarnate either returns a servant to handle the request or raises an exception. If the object ID is not in the Active Object Map and if no ServantActivator has been registered with the POA, the POA raises the CORBA::OBJ_ADAPTER system exception. The reason that the POA does not raise the CORBA::OBJECT_NOT_EXIST exception under these circumstances is that setting USE_SERVANT_MANAGER without registering a servant manager is an error in the application. The result is that the POA does not know for certain that the target object no longer exists, and so is not in a position to correctly raise the OBJECT_NOT_EXIST exception.
435