- •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++
NoFactory, InvalidCriteria, CannotMeetCriteria
);
};
};
The create_object operation can be used to implement a factory that can create any type of object given appropriate parameters. We can supply an unlimited amount of information to create_object (because the name-value sequence passed in the_criteria is unbounded) to guide the operation in how to create the new object. A likely implementation would be that create_object would not directly create a new object but instead would use the parameters to decide how to delegate the invocation to a more specific factory that actually knows how to create the object.
Because create_object must be able to return references to arbitrary types of objects, the return type is Object.
The supports operation should return true if the generic factory could create a new object if passed the same key. It returns false otherwise.
12.5 A Critique of the Life Cycle Service
It is instructive to examine a few of the design decisions made in the Life Cycle Service and the consequences of these decisions.
12.5.1 Generality of Design
By necessity, the Life Cycle Service is very general. The service must provide IDL interfaces that permit clients to control the life cycle of objects without knowing anything about the types or the semantics of the objects in question.
The design of the service clearly reflects this requirement for generality. For object creation, you can either use the GenericFactory interface or follow one of the factory design patterns discussed in Section 12.3. If you choose the GenericFactory interface, you must supply the parameters to the factory as namevalue pairs. As you will see in Chapter 15, values of type any are extremely flexible and powerful, but they are not nearly as easy to use as strongly typed values are. Moreover, even though type any is type-safe at run time, it is by necessity not type-safe at compile time. In other words, using type any replaces static compile-time type safety with dynamic run-time type safety. As a result, type mismatches are not detected until run time and are detected only if they actually occur. (In other words, they are detected only if we happen to have a test case that exposes the type mismatch.)
Specific create operations, such as the create_thermometer operation you saw in Section 12.3, do not suffer from these problems. The object reference returned from a
494
IT-SC book: Advanced CORBA® Programming with C++
specific factory operation can be strongly typed, whereas a generic factory must by necessity return the reference as type Object. The generic return type forces the receiving client to narrow the reference to its actual type, something that is inconvenient and not statically type-safe.
The copy and move operations present the same trade-offs as the generic factory. Parameters are not statically type-safe, and the return value from copy is weakly typed (LifeCycleObject instead of a specific interface type, such as Thermometer).
12.5.2 Date of Publication
The Life Cycle Service was one of the first services to be defined and published by the OMG and, in some ways, is showing its age. For example, the FactoryFinder interface provides a generic hook to implement a selection mechanism that can choose one or more factories that are suitable to create the required object. Although this approach is valid, the problem is that it is too generic. In addition, creating an even halfway sophisticated factory finder can be as much work as building an entire application. As a result, we must make do with a simple factory finder unless we are prepared to expend a lot of effort.
More recently, in 1997, the OMG published an updated version of the CORBAservices specification [21]. This document defines the OMG Trading Service, which provides a powerful and flexible object discovery mechanism. A trader can (among many other things) act as a generic factory finder. The significant advantage of the trader is that you do not have to implement it yourself. It also provides interfaces that are far more powerful and flexible than a simple generic factory, yet they do not compromise type safety to the same degree. We discuss the OMG Trading Service in detail in Chapter 19.
12.5.3 Problems with the move Operation
The move operation presents two types of problems. One type of problem is conceptual, and the other is technical.
Conceptual Problems with move
The move operation is intended to enable object migration. In other words, a client can use it to direct an object to disappear from one server and to reappear in another. Even assuming that we have supplied sufficient information in the the_criteria parameter as to where and how the object should move, there are still serious conceptual issues associated with the idea of migration.
The notion of object migration does not rest easy with the CORBA object model. One of the central features of CORBA, as we point out in Chapter 2, is the notion of location transparency. In fact, CORBA does not embody the concept of object location in its
495
IT-SC book: Advanced CORBA® Programming with C++
object model at all. Instead, CORBA goes to great lengths to hide the location of an object from clients and provides a notion of object identity that, together with an object's location, is encapsulated inside object references. Attempts by application code to look inside an object reference to find out "where" it points to are illegal.
This raises the question of whether an operation such as move even makes sense within the object model. If the object model has no sense of "here" and "there," why would clients, who are also part of the object model, want to move an object? To impart meaning to the idea of object location, we must step out of the system and look at it from a different level of abstraction. In other words, it is probably better to treat object location as an administrative aspect of CORBA rather than try to deal with it from inside the object model.
The server to which an object is moved may support the same protocol as the original server, but the client that instructs an object to move itself may not support the target server's protocol. In other words, to guarantee that after a move the client will not lose connectivity to the object, the client would have to have knowledge of both the original server's and the target server's protocols. However, making that knowledge available to the client destroys the protocol transparency of the CORBA object model.
The object to be moved may have persistent state in a database. Assuming that you can redirect the reference to the object to now denote an object in a different server, the question remains of how the persistent state for the object can be moved. Unless the source and the target server share a common data-base, it is difficult to see how this could be achieved without manual intervention.
You can treat the move operation as a logical copy of the physical object state, but you must be careful about its semantics. The CORBA object model requires that a particular object reference must denote the same object throughout the object's lifetime. After an object is destroyed, all its references must become permanently non-functional. This means that an object's identity must not change during the move, and that the fact of its moving must be undetectable to all clients in the system as far as the object's semantics are concerned. If you are not extremely careful, you might unwittingly violate this rule if some small detail of the object's state that is visible to clients is affected by the move.
The move operation raises the issue of object identity. This topic is full of pitfalls and is very difficult to define precisely. Object identity periodically becomes the subject of raging debate in the OMG, and it seems unlikely that agreement will ever arise from these discussions.
The issue is similar to a vexing identity question examined by philosophers (and science fiction writers!). If we were to record the complete physical makeup of all the matter in a person (in other words, completely capture the state of a person), we could destroy the person and keep only a recording of the person's state. Assuming that later, by some miracle of technology, we could completely rebuild the state of the person so that the
496
IT-SC book: Advanced CORBA® Programming with C++
person comes back to life, does the reconstructed person have the same identity as the destroyed person? If so, where was that person's identity while the person was destroyed? Clearly, the topic of identity has strong metaphysical and religious connotations, so we will not pursue it further here. Suffice it to say that object identity is under application control and can therefore mean whatever is most suitable to the application. For an outstanding treatment of these and related questions, see [6].
Technical Problems with move
Apart from conceptual issues, there are also a number of technical problems with move. Because of CORBA's implementation and language transparency, when a client moves an object, the server at the original location and the server at the target location might use different CPU architectures or implementation languages. This raises the question of how the object could physically move in this case. At the very least, the source and the target server would have to have made prior arrangements for object migration by providing equivalent implementations of the object's behavior that happen to use different platforms and languages. This point illustrates that object migration is limited to precise and prearranged circumstances.
The specification of move requires that the object reference for the moved object remain functional (that is, that it "follow" the object to its new location). As you will see in Chapter 14, many ORBs are physically incapable of moving a single object from one location to another without also invalidating the object's reference. Even if an ORB supports migration of a single object, the feature presents serious challenges with respect to an ORB's performance and scalability. The implication is that move is unimplementable in at least the general case.
12.5.4 Interface Granularity
Recall from Section 12.4 that the way to support life cycle operations is to inherit from the LifeCycleObject interface, which provides the copy, move, and remove operations. The problem with this design is that if we inherit from LifeCycleObject at all, we inherit all three operations. For our thermostats and thermometers, that is bad news, because these devices support neither copy nor move semantics.
The specification states that if a particular operation, such as copy, does not apply to an object, the operation can raise either the NotCopyable exception or the NO_IMPLEMENT system exception. However, why would an object offer an operation if that operation always and unconditionally raises an exception when a client calls it? It is far preferable in most cases not to provide the operation in the first place because then type checking can take place at compile time.
The problem created by LifeCycleObject is that the granularity of the object model is too coarse. It would have been better to define three abstract interfaces, such as Removable, Copyable, and Movable, so that applications could use them as mix-in interfaces to compose the required functionality. A tempting approach to address the
497
IT-SC book: Advanced CORBA® Programming with C++
deficiency of LifeCycleObject would be to add the three mix-in interfaces to the CosLifeCycle module and to change the definition of LifeCycleObject to inherit from the three mix-ins:
module CosLifeCycle { // Hypothetical IDL only!
// ...
interface Removable {
void remove() raises(NotRemovable);
}; |
|
|
|
interface Copyable { |
|
|
|
Copyable |
copy( |
|
there, |
|
in FactoryFinder |
||
|
in Criteria |
the_criteria |
|
|
) raises( |
|
|
|
NoFactory, NotCopyable, |
||
|
InvalidCriteria, CannotMeetCriteria |
||
}; |
); |
|
|
|
|
|
|
interface Movable { |
|
|
|
void |
move( |
|
there, |
|
in FactoryFinder |
||
|
in Criteria |
the_criteria |
|
|
) raises( |
|
|
|
NoFactory, NotMovable, |
||
|
InvalidCriteria, CannotMeetCriteria |
||
);
};
interface LifeCycleObject : Removable, Copyable, Movable { // Empty
}; // ...
};
Unfortunately, the CORBA type system does not allow this. You cannot make any change to the definition of an existing IDL type even if you were to change its repository ID. After an IDL definition is published, it becomes immutable. The reason is that any change, no matter how innocuous, can break existing client code. For example, if you were to build a client that uses the preceding hypothetical IDL and then were to recompile the client using a different ORB that provided the original version, the code would not compile. Because of the lack of a versioning mechanism in CORBA, IDL deficiencies are difficult to address except by creating new definitions in a different module.
12.5.5 Should You Use the Life Cycle Service?
The Life Cycle Service has a number of deficiencies. Some of them, such as weak type safety, are a necessary consequence of the service's generality. Other deficiencies, such as
498
