Now for the moment we've all been waiting for. You've likely been reading this chapter wondering just how a client obtains an IStorage pointer to the root object of a hierarchy. The answer is simple: OLE provides four API functions that create a compound file on whatever LockBytes object you want. Two of the functions create a new file, and two open an existing file:
API Function | Description |
StgCreateDocfile* | Opens a new compound file, given a filename and access mode flags, using the default file-based LockBytes. A compound file can be opened as transacted or direct. This function will generate a temporary file if a NULL filename is specified. If the file already exists, this function can either fail or overwrite the existing file, depending on the flags you pass. |
StgCreateDocfileOnILockBytes | Opens a new compound file on a given LockBytes object; otherwise, acts the same as StgCreateDocfile. |
StgOpenStorage | Opens an existing compound file given a filename or creates a new file in the same way as StgCreateDocfile. |
StgOpenStorageOnILockBytes | Opens an existing compound file whose bits exist in a given LockBytes object; otherwise, acts the same as StgOpenStorage. The STGM_CREATE flag is not allowed with this function. You must use StgCreateDocfileOnILockBytes to create a new storage, after which this function can open that storage. |
* The name Docfile is an archaic term for a compound file and has been preserved in these function names for compatibility with the initial beta releases of OLE 2.
The *OnLockBytes functions give you the ability to create a compound file anywhere you want instead of on the file system. For example, you might want to send the data across a network to a database without ever having to bother the storage object about the details. No matter what your LockBytes does, the IStorage pointer you get back from any of these four functions is equivalent to any other: the available functionality is identical.
Both StgOpenStorage and StgOpenStorageOnILockBytes take some extra arguments to deal with transaction optimizations, allowing a client to exclude specific elements in the storage hierarchy from being transacted even though the compound file itself is transacted. By excluding such elements, you reduce the overall amount of memory necessary to record changes to your data. For more information, refer to the OLE Programmer's Reference. This chapter does not go into more detail about this topic.
Besides the four create/open functions, OLE provides three other useful functions:
API Function | Description |
StgIsStorageFile | Tests whether a given file is a compound file |
StgIsStorageLockBytes | Tests whether the data in a given LockBytes object contains a compound file |
StgSetTimes | Provides the SetElementTimes equivalent for a root storage without having to open the storage |
An important note is that a compound file stores the filename as provided by the calling client without converting it to uppercase or lowercase. However, all filename comparisons made under Windows are case insensitive. In addition, OLE will typically use three file handles in opening a transacted compound file—one for the file, one for a temporary file in which changes are recorded, and one preallocated handle for low-memory save situations. Let's see why this third handle is important.