For the soul there is never birth nor death. It is not slain when the body is slain.
—Lord Krishna, Bhagavad Gita, 2:25
Do not be afraid of those who kill the body but cannot kill the soul.
—Jesus, The NIV Bible, Matthew 10:28a
If I am killed, it destroys merely the clay garment, the body. But if I kill, it injures the reality, the soul!
—Peace Pilgrim, being questioned by the FBI1
The concept of an eternal soul is part of the doctrines of many world religions and reflected in the writings of many mystics. The body is temporary, the soul is enduring—or what we might call persistent. Whatever another person might do to your body, they cannot touch the soul.
Without wandering off too much into theological speculation, we can think of many objects as having a body and a soul. The body is the volatile instantiation of the object's data structures and function tables in memory. These are volatile—that is, temporary—because they are destroyed when the computer is turned off or when a process simply terminates. Granted, for many objects this is no problem—their entire state is re-created as needed from instantiation to instantiation. These are objects without a soul.
Other objects, however, need to preserve some of their state information from instantiation to instantiation, much like a soul in the Vedic and Hindu traditions of reincarnation transmigrates from being to being, life after life, carrying with it the saintly or sinful acts of all past lives. In OLE, we call the information carried across object lifetimes the persistent state of that object. This muddles our understanding of an object—is it simply a set of memory structures, is it this persistent state, or is it both? To be precise, what we call a persistent object is some object that can somehow save its persistent state so that a later instantiation of the same class can reload that state and re-create the original object exactly. The memory structures that hold the information will change in the process, but the state remains the same. The body changes, but the soul is steady.
When an object exists as nothing more than its persistent state (that is, it occupies no memory and no pointers exist to its interfaces), we still call it an object for convenience, but we call it passive. When we create an instance of that object's class and ask it to initialize itself with the persistent state, that object is loaded, or running. The loading process brings the object's persistent state into memory, loads code that knows how to work with it, and makes interface pointers available to clients so they can access the running object.
In Chapter 7, we described OLE's implementation of the Structured Storage model and how to work with storage and stream elements. In the early part of that chapter, we saw how the idea of a "file system within a file" is necessary to allow multiple components to write their persistent information into the same underlying disk file. But a few important questions remain. How does a component or an object in a component become aware of the IStorage or IStream pointer that it should use in order to read and write its information? How does a client tell an object to make a transition between passive and active states?
The answers to these questions are what we call persistence models, described through the interfaces IPersistStorage, IPersistStream, and IPersistStreamInit, as well as the interface IPersistFile (which works along lines similar to the other interfaces but outside the scope of storage and stream elements). Whenever an object provides any of the IPersist* interfaces through its QueryInterface function, that object tells the world that it can read and write its persistent state to a storage, stream, or file according to the contract implied by the interface. In other words, the object says that it has some things it would like to take with it into its next life, and the client is responsible for telling the object to write or read its persistent state at the appropriate times. (The client declares which persistence model is used.) The process of running a persistent object from its passive state is simply one of creating an instance of the object with CoCreateInstance and having that object initialize itself from the contents of some storage medium, through member functions of the IPersist* interfaces.
After we take a look at the IPersist* interfaces themselves, their member functions, and their rules of use and implementation, we'll see all but IPersistFile implemented in the Polyline component that we first saw in Chapter 5. Component Cosmo is also updated from the Chapter 5 version to work with this new Polyline, through both storage-based and stream-based persistence models. The sample code in this chapter forms a good framework for other implementations of these interfaces that we'll see in later chapters. As for IPersistFile, we'll see an implementation of it in Chapter 9, as file monikers are particularly interested in that interface.
An object's persistent state, of course, really isn't as eternal as various religions' descriptions of the soul. As Buddhists might tell you, the soul, being changeable, cannot be permanent because there is no immortal survival for a changing thing. Certainly the persistent state of an object may change, and it may disappear altogether. A client (or end user) that ultimately controls the storage may, at any time, obliterate that persistent state. But until that time, an object wants to keep its soul alive through many transitions between its passive and running states. The various IPersist* interfaces are the means to such endurance.
1 From Peace Pilgrim Her Life and Works in Her Own Words Ocean Tree Books Santa Fe NM.Peace Pilgrim was a woman (known by no other name) who walked across countries and continents (primarily the United States) from 1953 to 1981 carrying the message "This is the way of peace—overcome evil with good and falsehood with truth and hatred with love. When enough of us find inner peace our institutions will become more peaceful and there will be no more occasion for war." |