The simplest persistence model for an object is for it to read and write its information from a single stream. A moniker is an example of such an object (as we'll see in Chapter 9), as are a variety of OLE controls. The IPersistStream interface describes the semantics of simple load and save operations, and IPersistStreamInit improves on this idea slightly by adding the extra InitNew initialization function:
interface IPersistStream : IPersist
{
//IUnknown members and GetClassID from IPersist
HRESULT IsDirty(void);
HRESULT Load(IStream *pStm);
HRESULT Save(IStream *pStm, BOOL fClearDirty);
HRESULT GetSizeMax(ULARGE_INTEGER *pcbSize);
}; interface IPersistStreamInit : IPersist
{
[All other functions identical to IPersistStream]
HRESULT InitNew(void);
};
These functions behave as follows:4
Member Function | Description |
IsDirty | Indicates whether the object considers itself dirty. If the object is dirty, the client should call Save before releasing the object. |
Load | Instructs the object to load its persistent data from the current seek offset of the given stream. Clients are allowed to serialize the data from multiple objects into a single stream. The object must read its data and return, and it cannot hold the IStream pointer outside this function. |
Save | Instructs the object to save its persistent data at the current seek offset of the given stream. The object must save its data and return without holding the IStream pointer. The fClearDirty argument tells the object whether it should consider itself clean after this call—a client may be making a copy of the object as opposed to saving its data permanently, and this flag distinguishes the cases. |
GetSizeMax | Asks the object to return the maximum amount of information it would possibly write through a call to Save. This allows a calling client to preset the size of a stream (IStream::SetSize), especially if it is saving the data of multiple objects in the same stream or is otherwise planning to provide an IStream that cannot enlarge itself automatically. For this reason, the implementation of GetSizeMax should return the maximum upper boundary of the object's storage requirements. |
InitNew | Initializes the object after creation, giving one object |
(IPersistStreamInit) | an opportunity to set its initial state. |
IPersistStream and IPersistStreamInit can both act as initialization interfaces. The Load function of either interface and the InitNew function of IPersistStreamInit are the specific initialization functions. IPersistStreamInit was specially created to provide a stream-based object with a way to know that it has been newly created and has no persistent data as yet, a requirement for certain OLE controls.
It is important to understand the locality of Load and Save calls. First, the object cannot hold a copy of the IStream pointer outside the scope of these functions, and second, the object is allowed to work only with the stream between the current seek offset on entry to the function and that offset plus whatever the object returns from GetSizeMax. In short, these rules mean that the calling client has ultimate control over which object's data is stored in what parts of the stream. As mentioned in Chapter 7, a stream in a compound file is allocated on a 512-byte granularity, so clients generally try to utilize all those bytes if they can. The locality of Load and Save enables clients to do this even with multiple stream-based objects, each of which may need only a few dozen bytes of the overall stream. These rules also work well with functions such as ReadClassStm and WriteClassStm, so a client can associate CLSIDs for each object's data, anywhere in the stream. In addition, the client can write its own CLSID at the beginning of the stream, identifying itself as the code that understands the layout of information in the rest of the stream.
OLE provides two API functions that wrap these various sequences. OleSaveToStream calls WriteClassStm followed by IPersistStream::Save on a given object. OleLoadFromStream will call ReadClassStm on a given stream, instantiate an object of that class with CoCreateInstance, then have that new object initialize itself through IPersistStream::Load. A client can use both these API functions to save and load sequential objects in a single stream.
4 While IPersistStreamInit is not derived directly from IPersistStream it has exactly the same member function signatures as the members of IPersistStream. Therefore IPersistStreamInit is polymorphic with IPersistStream. A pointer to IPersistStreamInit can be used as a pointer to IPersistStream. |