- •Advanced CORBA® Programming with C++
- •Review
- •Dedication
- •Preface
- •Prerequisites
- •Scope of this Book
- •Acknowledgments
- •Chapter 1. Introduction
- •1.1 Introduction
- •1.2 Organization of the Book
- •1.3 CORBA Version
- •1.4 Typographical Conventions
- •1.5 Source Code Examples
- •1.6 Vendor Dependencies
- •1.7 Contacting the Authors
- •Part I: Introduction to CORBA
- •Chapter 2. An Overview of CORBA
- •2.1 Introduction
- •2.2 The Object Management Group
- •2.3 Concepts and Terminology
- •2.4 CORBA Features
- •2.5 Request Invocation
- •2.6 General CORBA Application Development
- •2.7 Summary
- •Chapter 3. A Minimal CORBA Application
- •3.1 Chapter Overview
- •3.2 Writing and Compiling an IDL Definition
- •3.3 Writing and Compiling a Server
- •3.4 Writing and Compiling a Client
- •3.5 Running Client and Server
- •3.6 Summary
- •Part II: Core CORBA
- •Chapter 4. The OMG Interface Definition Language
- •4.1 Chapter Overview
- •4.2 Introduction
- •4.3 Compilation
- •4.4 Source Files
- •4.5 Lexical Rules
- •4.6 Basic IDL Types
- •4.7 User-Defined Types
- •4.8 Interfaces and Operations
- •4.9 User Exceptions
- •4.10 System Exceptions
- •4.11 System Exceptions or User Exceptions?
- •4.12 Oneway Operations
- •4.13 Contexts
- •4.14 Attributes
- •4.15 Modules
- •4.16 Forward Declarations
- •4.17 Inheritance
- •4.18 Names and Scoping
- •4.19 Repository Identifiers and pragma Directives
- •4.20 Standard Include Files
- •4.21 Recent IDL Extensions
- •4.22 Summary
- •Chapter 5. IDL for a Climate Control System
- •5.1 Chapter Overview
- •5.2 The Climate Control System
- •5.3 IDL for the Climate Control System
- •5.4 The Complete Specification
- •Chapter 6. Basic IDL-to-C++ Mapping
- •6.1 Chapter Overview
- •6.2 Introduction
- •6.3 Mapping for Identifiers
- •6.4 Mapping for Modules
- •6.5 The CORBA Module
- •6.6 Mapping for Basic Types
- •6.7 Mapping for Constants
- •6.8 Mapping for Enumerated Types
- •6.9 Variable-Length Types and _var Types
- •6.10 The String_var Wrapper Class
- •6.11 Mapping for Wide Strings
- •6.12 Mapping for Fixed-Point Types
- •6.13 Mapping for Structures
- •6.14 Mapping for Sequences
- •6.15 Mapping for Arrays
- •6.16 Mapping for Unions
- •6.17 Mapping for Recursive Structures and Unions
- •6.18 Mapping for Type Definitions
- •6.19 User-Defined Types and _var Classes
- •6.20 Summary
- •Chapter 7. Client-Side C++ Mapping
- •7.1 Chapter Overview
- •7.2 Introduction
- •7.3 Mapping for Interfaces
- •7.4 Object Reference Types
- •7.5 Life Cycle of Object References
- •7.6 Semantics of _ptr References
- •7.7 Pseudo-Objects
- •7.8 ORB Initialization
- •7.9 Initial References
- •7.10 Stringified References
- •7.11 The Object Pseudo-Interface
- •7.12 _var References
- •7.13 Mapping for Operations and Attributes
- •7.14 Parameter Passing Rules
- •7.15 Mapping for Exceptions
- •7.16 Mapping for Contexts
- •7.17 Summary
- •Chapter 8. Developing a Client for the Climate Control System
- •8.1 Chapter Overview
- •8.2 Introduction
- •8.3 Overall Client Structure
- •8.4 Included Files
- •8.5 Helper Functions
- •8.6 The main Program
- •8.7 The Complete Client Code
- •8.8 Summary
- •Chapter 9. Server-Side C++ Mapping
- •9.1 Chapter Overview
- •9.2 Introduction
- •9.3 Mapping for Interfaces
- •9.4 Servant Classes
- •9.5 Object Incarnation
- •9.6 Server main
- •9.7 Parameter Passing Rules
- •9.8 Raising Exceptions
- •9.9 Tie Classes
- •9.10 Summary
- •Chapter 10. Developing a Server for the Climate Control System
- •10.1 Chapter Overview
- •10.2 Introduction
- •10.3 The Instrument Control Protocol API
- •10.4 Designing the Thermometer Servant Class
- •10.5 Implementing the Thermometer Servant Class
- •10.6 Designing the Thermostat Servant Class
- •10.7 Implementing the Thermostat Servant Class
- •10.8 Designing the Controller Servant Class
- •10.9 Implementing the Controller Servant Class
- •10.10 Implementing the Server main Function
- •10.11 The Complete Server Code
- •10.12 Summary
- •Chapter 11. The Portable Object Adapter
- •11.1 Chapter Overview
- •11.2 Introduction
- •11.3 POA Fundamentals
- •11.4 POA Policies
- •11.5 POA Creation
- •11.6 Servant IDL Type
- •11.7 Object Creation and Activation
- •11.8 Reference, ObjectId, and Servant
- •11.9 Object Deactivation
- •11.10 Request Flow Control
- •11.11 ORB Event Handling
- •11.12 POA Activation
- •11.13 POA Destruction
- •11.14 Applying POA Policies
- •11.15 Summary
- •Chapter 12. Object Life Cycle
- •12.1 Chapter Overview
- •12.2 Introduction
- •12.3 Object Factories
- •12.4 Destroying, Copying, and Moving Objects
- •12.5 A Critique of the Life Cycle Service
- •12.6 The Evictor Pattern
- •12.7 Garbage Collection of Servants
- •12.8 Garbage Collection of CORBA Objects
- •12.9 Summary
- •Part III: CORBA Mechanisms
- •Chapter 13. GIOP, IIOP, and IORs
- •13.1 Chapter Overview
- •13.2 An Overview of GIOP
- •13.3 Common Data Representation
- •13.4 GIOP Message Formats
- •13.5 GIOP Connection Management
- •13.6 Detecting Disorderly Shutdown
- •13.7 An Overview of IIOP
- •13.8 Structure of an IOR
- •13.9 Bidirectional IIOP
- •13.10 Summary
- •14.1 Chapter Overview
- •14.2 Binding Modes
- •14.3 Direct Binding
- •14.4 Indirect Binding via an Implementation Repository
- •14.5 Migration, Reliability, Performance, and Scalability
- •14.6 Activation Modes
- •14.7 Race Conditions
- •14.8 Security Considerations
- •14.9 Summary
- •Part VI: Dynamic CORBA
- •Chapter 15 C++ Mapping for Type any
- •15.1 Chapter Overview
- •15.2 Introduction
- •15.3 Type any C++ Mapping
- •15.4 Pitfalls in Type Definitions
- •15.5 Summary
- •Chapter 16. Type Codes
- •16.1 Chapter Overview
- •16.2 Introduction
- •16.3 The TypeCode Pseudo-Object
- •16.4 C++ Mapping for the TypeCode Pseudo-Object
- •16.5 Type Code Comparisons
- •16.6 Type Code Constants
- •16.7 Type Code Comparison for Type any
- •16.8 Creating Type Codes Dynamically
- •16.9 Summary
- •Chapter 17. Type DynAny
- •17.1 Chapter Overview
- •17.2 Introduction
- •17.3 The DynAny Interface
- •17.4 C++ Mapping for DynAny
- •17.5 Using DynAny for Generic Display
- •17.6 Obtaining Type Information
- •17.7 Summary
- •Part V: CORBAservices
- •Chapter 18. The OMG Naming Service
- •18.1 Chapter Overview
- •18.2 Introduction
- •18.3 Basic Concepts
- •18.4 Structure of the Naming Service IDL
- •18.5 Semantics of Names
- •18.6 Naming Context IDL
- •18.7 Iterators
- •18.8 Pitfalls in the Naming Service
- •18.9 The Names Library
- •18.10 Naming Service Tools
- •18.11 What to Advertise
- •18.12 When to Advertise
- •18.13 Federated Naming
- •18.14 Adding Naming to the Climate Control System
- •18.15 Summary
- •Chapter 19. The OMG Trading Service
- •19.1 Chapter Overview
- •19.2 Introduction
- •19.3 Trading Concepts and Terminology
- •19.4 IDL Overview
- •19.5 The Service Type Repository
- •19.6 The Trader Interfaces
- •19.7 Exporting Service Offers
- •19.8 Withdrawing Service Offers
- •19.9 Modifying Service Offers
- •19.10 The Trader Constraint Language
- •19.11 Importing Service Offers
- •19.12 Bulk Withdrawal
- •19.13 The Admin Interface
- •19.14 Inspecting Service Offers
- •19.15 Exporting Dynamic Properties
- •19.16 Trader Federation
- •19.17 Trader Tools
- •19.18 Architectural Considerations
- •19.19 What to Advertise
- •19.20 Avoiding Duplicate Service Offers
- •19.21 Adding Trading to the Climate Control System
- •19.22 Summary
- •Chapter 20. The OMG Event Service
- •20.1 Chapter Overview
- •20.2 Introduction
- •20.3 Distributed Callbacks
- •20.4 Event Service Basics
- •20.5 Event Service Interfaces
- •20.6 Implementing Consumers and Suppliers
- •20.7 Choosing an Event Model
- •20.8 Event Service Limitations
- •20.9 Summary
- •Part VI: Power CORBA
- •Chapter 21. Multithreaded Applications
- •21.1 Chapter Overview
- •21.2 Introduction
- •21.3 Motivation for Multithreaded Programs
- •21.4 Fundamentals of Multithreaded Servers
- •21.5 Multithreading Strategies
- •21.6 Implementing a Multithreaded Server
- •21.7 Servant Activators and the Evictor Pattern
- •21.8 Summary
- •22.1 Chapter Overview
- •22.2 Introduction
- •22.3 Reducing Messaging Overhead
- •22.4 Optimizing Server Implementations
- •22.5 Federating Services
- •22.6 Improving Physical Design
- •22.7 Summary
- •Appendix A. Source Code for the ICP Simulator
- •Appendix B. CORBA Resources
- •Bibliography
IT-SC book: Advanced CORBA® Programming with C++
Chapter 12. Object Life Cycle
12.1 Chapter Overview
This chapter covers the broad topic of object life cycle: how objects can be created, copied, moved, and destroyed. Sections 12.3 to 12.5 discuss the OMG Life Cycle Service, which provides a few design guidelines for how to define life cycle operations. Section 12.6 discusses the Evictor pattern. It is important because it permits you to limit memory consumption of servers that implement large numbers of objects. The chapter concludes with Sections 12.7 and 12.8, which discuss garbage collection strategies in a CORBA environment.
Object life cycle is one of the most challenging topics in distributed systems, and we suggest you read this chapter in detail. Much of the information presented here is essential for building scalable and reliable applications.
12.2 Introduction
The climate control system developed in Chapter 11 has one drawback: there is no apparent way for a client to connect a newly installed device to the system. The controller could automatically discover new devices as they are connected to the network, but our hypothetical instrument control protocol does not offer this functionality. So the question arises, "How can we tell the climate control system that there is a new device on the network so that the server can instantiate a new CORBA object for the device?"
This question is part of a topic generally known as object life cycle. Object life cycle addresses the issues of
Object creation Object destruction Object copying Object movement
The CORBAservices [21] Life Cycle Service addresses these issues. Unlike other OMG services, the Life Cycle Service is not a service that can be built by a vendor and simply used by clients. Instead, the Life Cycle Service describes a number of interfaces and design patterns you can choose to use for life cycle management of objects. In other words, the Life Cycle Service is largely a set of recommendations and not an implementable specification.
About two-thirds of the Life Cycle specification is composed of a number of nonnormative addenda. These addenda cover the Compound Life Cycle specification, filters, administration, and support for objects in a Portable Common Tool Environment (PCTE) [3]. However, we are not aware of significant use of these addenda in current software
468
IT-SC book: Advanced CORBA® Programming with C++
development projects, so we cover only the main part of the Life Cycle specification in this chapter.
12.3 Object Factories
The OMG Life Cycle specification recommends that CORBA applications use the Factory pattern [4] to create objects. A factory is a CORBA object that offers one or more operations to create other objects. To create a new object, a client invokes an operation on the factory; the operation's implementation creates a new CORBA object and returns a reference for the new object to the client. Factory operations in a distributed system play the role of the constructor in C++. The difference is that a factory operation creates a CORBA object in a possibly remote address space, whereas a C++ constructor always creates a C++ object in the local address space. Also, you invoke factory operations on existing objects, whereas you can invoke constructors without already having an existing object.
Object creation is highly specific to the type of object being created. The actions taken by a factory vary greatly depending on whether it creates a document object, a person object, or a thermometer object. The type of object being created determines which parameters must be passed by the client. (Clearly, a client would pass different parameters to create a person object than it would to create a thermometer object.)
To make all this more concrete, we will add factories to the climate control system. Here is one possible approach:
#pragma prefix "acme.com"
module CCS {
// ...
exception DuplicateAsset {};
interface ThermometerFactory {
Thermometer create(in AssetType anum, in LocType loc) raises(DuplicateAsset);
};
interface ThermostatFactory {
Thermostat |
create( |
anum, |
in |
AssetType |
|
in |
LocType |
loc, |
in |
TempType |
temp |
) raises(DuplicateAsset, Thermostat::BadTemp);
};
};
We have added two new interfaces to the specification. ThermometerFactory offers a create operation to create a new thermometer, and ThermostatFactory offers a create operation to create a new thermostat.
469
IT-SC book: Advanced CORBA® Programming with C++
If we have just installed a new thermometer in the climate control system, we inform the system of the new thermometer's existence by calling the create operation on the ThermometerFactory interface. In response, the factory creates a new object reference for the device and returns it.
Recall from Chapter 10 that the thermometer and thermostat devices have a unique asset number that is also used as the ICP network address. In addition, each device has a modifiable location attribute. We must pass these two items of information to the create operation. We pass an asset number because it informs the climate control system of the identity of the new device (that is, its network address), and we pass a location because the location is part of the initial state of the object. The implementation of create programs the location string into the new thermometer. Thermometers also have a model and a temperature attribute, but there is no point in passing values for these attributes to create. The model string is permanently programmed into the device itself and is therefore read-only. And, of course, there is no point in passing a temperature because it does not make sense to tell a thermometer what temperature it should report.
To create a thermostat, the client must supply an additional parameter: the initial temperature setting for that thermostat. Again, the implementation of the create operation takes care of programming that nominal temperature into the device.
Both create operations can raise a DuplicateAsset exception. We need this exception because if we were to permit two devices with the same asset number on the network, the controller could no longer distinguish between them. The create operation for thermostats can also raise a BadTemp exception to indicate that the requested initial temperature is out of range.
12.3.1 Factory Design Options
There are many different options for factory design, and the basic Factory pattern we showed in the preceding section is only one of them.
Combined Factory
Instead of using two separate interfaces, we can use a single factory to create both types of devices:
#pragma prefix "acme.com"
module CCS {
// ...
exception DuplicateAsset {};
interface DeviceFactory { Thermometer create_thermometer(
in |
AssetType |
anum, |
in |
LocType |
loc |
470
IT-SC book: Advanced CORBA® Programming with C++
)raises(DuplicateAsset);
Thermostat create_thermostat( in AssetType anum, in LocType loc,
in TempType temp
)raises(DuplicateAsset, Thermostat::BadTemp);
};
};
This design is just as valid as the previous one but has different architectural consequences. With this new design, a single factory object must be able to create both thermometers and thermostats, whereas with the first design, each factory had knowledge only of a single device. The main consequence of combining the factory operations into a single interface is that it becomes harder to distribute our system over multiple server processes. For example, we might want an architecture such as the one shown in Figure 12.1.
Figure 12.1 A distributed climate control system.
This architecture could be useful, for example, if we decide to buy thermometers and thermostats from different manufacturers that use incompatible instrument control protocols. In this case, it might be necessary to split our system into server processes as shown because, for example, the libraries for the two proprietary protocols might not be available for the same platform. (This example is not as contrived as it may appear. This sort of thing happens much more often in real IT environments than anyone would like to admit.)
471
IT-SC book: Advanced CORBA® Programming with C++
By combining the factory operations into a single interface, we have made it harder to implement the factory. Logically, the factory still belongs with the controller server, but physically, the factory cannot exist only in the controller server; the POA does not allow us to create an object reference to an object in another address space. As you saw in Section 11.7.1, we can create an object reference by instantiating a servant and calling the _this member function, or we can create an object reference using the POA member function create_reference_with_id. However, no matter how we create the reference, it always denotes an object in the same server process as the calling code. Looking at our factory interface, it becomes clear that a factory inside the controller server cannot create a reference to a CORBA object in the thermometer server, at least not directly.
This is not a deficiency in the POA. To create a reference in one server that denotes an object in another server, we would have to supply information that is not easily obtainable, such as the physical address of the server, the protocol (including the protocol version) to be used to communicate with the server, and the object ID of the target object. Not only is it unlikely that we would have the relevant details at hand, but also, if the POA were to allow creation of object references to another server, it would place itself firmly outside the CORBA object model. Remember that object references are opaque and application code is not allowed to see the details of the addressing and protocol information that is embedded inside object references. Moreover, the whole point of CORBA is that we do not need to worry about things such as physical network addresses and protocols, so permitting the creation of object references to other address spaces simply does not make sense.
Nevertheless, you can have a factory that effectively creates object references for objects in another address space: the factory must delegate the creation to another factory that is collocated with the object it creates. For the architecture shown in Figure 12.1, a factory in the controller server would delegate creation of thermometers to a factory implemented in the thermometer server and would delegate the creation of thermostats to a factory implemented in the thermostat server.
Combined Collection and Factory
Yet another option, a variation on the preceding one, is to add the factory operations to the Controller interface instead of using a separate factory object:
#pragma prefix "acme.com"
module CCS {
// ...
interface Controller {
exception DuplicateAsset {};
Thermometer create_thermometer( in AssetType anum,
in LocType loc
472
IT-SC book: Advanced CORBA® Programming with C++
)raises(DuplicateAsset);
Thermostat create_thermostat( in AssetType anum, in LocType loc,
in TempType temp
)raises(DuplicateAsset, Thermostat::BadTemp);
// Other operations...
};
};
The actual operation definitions are identical to the preceding example, but we have moved them into the controller interface. Again, whether this is a reasonable design depends on the application and how we want to distribute it over physical server processes. By adding the factory operations to the Controller interface, we commit ourselves to implementing the factory operations and the collection-manager operations (such as list) in the same server process. This is not necessarily a bad thing; the Controller interface already acts as a collection manager for our devices, so we might as well have the device creation operations on that interface too. However, from a purist's perspective, we no longer have a clean separation of concerns. A pure object model would have separate factory and collection interfaces, and the collection interface would offer an operation to add a device to the collection.
The main point to keep in mind when designing interfaces is that you cannot split the implementation of a single interface across multiple server processes (at least not without resorting to explicit delegation). An IDL interface defines the smallest grain of distribution in the CORBA object model, and therefore the design of interfaces determines, at least in part, how a logical system can be partitioned over physical server processes.
Pure Collection and Factory
Here is the pure version of the object model:
#pragma prefix "acme.com"
module CCS {
// ...
exception DuplicateAsset {};
interface ThermometerFactory {
Thermometer create(in AssetType anum, in LocType loc) raises(DuplicateAsset);
};
interface ThermostatFactory { Thermostat create(
in AssetType anum, in LocType loc,
473
IT-SC book: Advanced CORBA® Programming with C++
in TempType temp
)raises(DuplicateAsset, Thermostat::BadTemp);
};
interface Controller {
exception DuplicateDevice {}; exception NoSuchDevice {};
void add_device(in Thermometer t); void remove_device(in Thermometer t);
// Other operations...
};
};
In this design, the factories and the controller are separate interfaces. Moreover, the controller now acts as a pure collection because it provides explicit add_device and remove_device operations. This design provides better separation of concerns: factories do nothing except create devices, and the controller is simply a collection of references to these devices. This design eliminates the hidden communication between the factory and the controller that was present in the previous designs. Instead of "magically" knowing about a newly created device, the controller is explicitly informed when a new device has been added.
The downside of this design is that it places more responsibility for keeping the object model consistent on either the client or the factory. If we place the responsibility for consistency on the client, the client must both explicitly create a new device and call the add_device operation to add the device to the controller. This makes the device creation process more complex for the client and requires two remote messages instead of one. Alternatively, if we place the responsibility for consistency on the factory, the factory must call the add_device operation. This simplifies device creation for clients but adds a dependency between the factory and the controller because the factory now must somehow know which controller to add the new device to.
This kind of trade-off is typical for object models. A pure model that offers better separation of concerns usually also requires more messages to be exchanged because in a pure model, objects do not share hidden state.
Bulk Factories
In the climate control system, it is unlikely that we will need to frequently add large numbers of devices. However, for more ephemeral objects—for example, objects representing Web pages—we may find that having to send a separate message for each object to be created is too slow. In this case, we can choose to define an operation that creates objects in bulk:
module CCS {
// ...
474
IT-SC book: Advanced CORBA® Programming with C++
exception DuplicateAsset {};
typedef sequence<Thermometer> ThermometerSeq;
struct InitTherm { AssetType anum; LocType loc;
};
typedef sequence<InitTherm> InitThermSeq;
interface BulkThermometerFactory { ThermometerSeq create(in InitThermSeq details)
raises(DuplicateAsset, Thermostat::BadTemp);
}; // ...
};
Instead of passing the initial state for a single thermometer to the create operation, we pass a sequence of InitTherm structures, one for each thermometer to be created. The operation creates as many CORBA objects as there are elements in the details sequence and returns their references.
The main advantage of this design is that it reduces messaging overhead and is therefore more efficient. On the downside, it makes error handling more complex. For example, if one of the InitTherm structures contains an invalid temperature, it is no longer clear which particular structure caused the problem unless we also add additional information to the data returned by a BadTemp exception. Your application may not need to distinguish the offending entry, but the example shows that bulk operations also add new failure semantics to the system that may require precise handling.
Bulk Factories without a Return Value
Yet another variation on the object creation theme is the following:
// As before...
interface BulkThermometerFactory {
void create(in InitThermSeq details) raises(DuplicateAsset, Thermostat::BadTemp);
};
The only difference between this version and the preceding version is that the create operation has no return value. The assumption built into this design is that after creating the devices, the client will use the list or find operation on the controller to acquire the device references. Again, whether this design is appropriate depends entirely on how we anticipate that the application will be used. For example, this version is appropriate if we use separate clients for the creation and the monitoring of devices. If special-purpose clients only create devices and do not monitor them, there is little point in returning object references to the clients because they would simply ignore them.
475
IT-SC book: Advanced CORBA® Programming with C++
Deciding On a Factory Design
The preceding discussion of design options applies not only to factories but also to almost every object system that uses more than one type of object. If the different objects in the system need to communicate with one another, the design of the IDL interfaces has profound influence on the system's ease of use for clients as well as its reliability, its performance, and its physical architecture.
If nothing else, the preceding discussion should make it clear that it pays to think before deciding on a particular interface design. In particular, an object model that may be perfectly appropriate inside a C++ program may disappoint you if you naively translate it into its IDL equivalent. What is appropriate for C++ is not necessarily appropriate for CORBA. In particular, the cost of sending a remote message over a network is orders of magnitude larger than the cost of a C++ method invocation. As a result, not only is it important for you to choose the correct communication model between interfaces, but it is also important that you correctly distribute interface instances over physical server processes. If you implement objects that require a high message exchange rate in different servers, performance will be reduced accordingly.
In a sense, the preceding should not come as a surprise. Interface design has profound influence on system performance in most environments, and CORBA is no exception. Because this book is not about object-oriented design, we say little more about this topic in the remaining chapters. You can consult any number of books to learn more. However, we briefly return to the cost of remote messages in Chapter 22.
12.3.2 Implementing Factories with C++
For the remainder of this chapter, we use persistent objects for our climate control system. In addition, we use a servant manager to bring servants into memory on demand. These choices require a number of changes to the climate control system.
The controller must maintain a list of asset numbers on secondary storage to keep track of known devices. This is necessary because otherwise, the list operation cannot be implemented (the ICP network does not support discovery). For this simple example, we read the complete list of asset numbers from a file in the controller's constructor and write the list of asset numbers back to the file in the destructor. (A more realistic application would update the list on secondary storage immediately when a device is added or removed.) You can find the class definition for the Controller_impl servant in Section 10.11.1. Here is the code for the constructor:
Controller_impl::
Controller_impl( PortableServer::POA_ptr poa, const char * asset_file
) throw(int) : m_poa(PortableServer::POA::_duplicate(poa)), m_asset_file(asset_file)
{
fstream afile(m_asset_file, ios::in|ios::out, 0666);
476
IT-SC book: Advanced CORBA® Programming with C++
if (!afile) {
cerr < < "Cannot open " < < m_asset_file < < endl; throw 0;
}
CCS::AssetType anum; while (afile >> anum)
m_assets[anum] = 0; afile.close();
if (!afile) {
cerr < < "Cannot close " < < m_asset_file < < endl; throw 0;
}
}
Note that the file name is passed to the constructor and is remembered in the private member variable m_asset_file. (We return to the purpose of the m_poa member shortly.) The constructor iterates over the input file (creating it if necessary) and inserts each asset number into the m_assets map with a null servant pointer. This action initializes the m_assets map with all known asset numbers. However, no servants are instantiated at this point. Instead, an asset number with a null servant pointer indicates that the device exists but has no servant in memory.
The controller's destructor runs when the server shuts down and writes the known asset numbers back to the file:
Controller_impl:: ~Controller_impl()
{
//Write out the current set of asset numbers
//and clean up all servant instances. ofstream afile(m_asset_file);
if (!afile) {
cerr < < "Cannot open " < < m_asset_file < < endl; abort();
}
AssetMap::iterator i;
for (i = m_assets.begin(); i != m_assets.end(); i++) { afile < < i->first < < endl;
if (!afile) {
cerr < < "Cannot update " < < m_asset_file < < endl; abort();
}
delete i->second;
}
afile.close(); if (!afile) {
cerr < < "Cannot close " < < m_asset_file < < endl; abort();
}
}
477
IT-SC book: Advanced CORBA® Programming with C++
Note that the loop also deletes each instantiated servant. (If a servant pointer is null, the delete does nothing.) This technique ensures that the destructor for all instantiated servants is invoked so that servants can properly finalize their state before the server shuts down.
For this example, we are using the combined collection and factory approach we describe on page 533, in which the controller offers a creation operation for each type of device. (The implementation of the factory operations for the other options we discussed is very similar.) Here is the code for create_thermometer:
CCS::Thermometer_ptr Controller_impl::
create_thermometer(CCS::AssetType anum, const char * loc) throw(CORBA::SystemException, CCS::Controller::DuplicateAsset)
{
//Make sure the asset number is new. if (exists(anum))
throw CCS::Controller::DuplicateAsset();
//Add the device to the network and program its location. if (ICP_online(anum) != 0)
abort();
if (ICP_set(anum, "location", loc) != 0) abort();
//Add the new device to the m_assets map.
add_impl(anum, 0);
// Create an object reference for the device and return it. return make_dref(m_poa, anum);
}
The code first checks whether a device having the asset number passed in already exists. If it does, the code throws a DuplicateAsset exception. (The exists function is a simple helper function that returns true if the asset number passed to it is in the m_assets map.) The next step is to inform the ICP network of the existence of the new device and to program its location string. Next, the code adds an entry for the new device to the m_assets map, storing a null pointer to the servant. In other words, the factory does not immediately instantiate a servant for the new device but instead delays instantiation until the first operation is invoked. (You will see how this works in Section 12.6.) The final step is to call the make_dref helper function, which creates an object reference for the new device.
Here is the code for make_dref:
static CCS::Thermometer_ptr make_dref(PortableServer::POA_ptr poa, CCS::AssetType anum)
{
// Convert asset number to OID. ostrstream ostr;
ostr < < anum < < ends;
char * anum_str = ostr.str(); PortableServer::ObjectId_var oid
= PortableServer::string_to_ObjectId(anum_str);
478
IT-SC book: Advanced CORBA® Programming with C++
ostr.rdbuf()->freeze(0);
//Look at the model via the network to determine
//the repository ID.
char buf[32];
if (ICP_get(anum, "model", buf, sizeof(buf)) != 0) abort();
const char * rep_id = strcmp(buf, "Sens-A-Temp") == 0
? "IDL:acme.com/CCS/Thermometer:1.0" : "IDL:acme.com/CCS/Thermostat:1.0";:
"IDL:acme.com/CCS/Thermostat:1.0";
// Make a new reference. CORBA::Object_var obj
= poa->create_reference_with_id(oid, rep_id); return CCS::Thermometer::_narrow(obj);
}
The make_dref function merely encapsulates similar code that is shown in Chapter 11. Note that we pass to make_dref an object reference to the POA for the new servant. That POA reference in turn is remembered by the controller's constructor, shown on page 543.
Looking at create_thermometer and make_dref, you can see that very little work is actually required to create a new object. The factory simply informs the network of the new device, updates the controller's notion of what devices exist, and creates an object reference for the new device.
The implementation of create_thermostat is similar. The main difference is that we must check whether the initial temperature setting is in range and that we must narrow the reference returned by make_dref to the correct type:
CCS::Thermostat_ptr |
|
Controller_impl:: |
|
create_thermostat( |
anum, |
CCS::AssetType |
|
const char * |
loc, |
CCS::TempType |
temp |
) throw( |
|
CORBA::SystemException,
CCS::Controller::DuplicateAsset,
CCS::Thermostat::BadTemp)
{
//Make sure the asset number is new. if (exists(anum))
throw CCS::Controller::DuplicateAsset();
//Add the device to the network and program its location. if (ICP_online(anum) != 0)
abort();
if (ICP_set(anum, "location", loc) != 0) abort();
479
