Again, a storage object is like a directory: it can contain any number of storages (subdirectories) and any number of streams (files). Because any substorage is a storage in itself, just as any subdirectory is always a directory, substorages might themselves contain more storages and more streams—ad nauseam—until you deplete your available storage space. But in and of themselves, storage objects do not hold data—they only hold streams that hold data.
The client of a storage object, be it an application or some other component itself, works with a storage through the IStorage members described in Table 7-1. As you can see, a storage can perform many of the operations that you'll find in command-line shells like COMMAND.COM, such as enumerate, copy, move, rename, and delete elements, as well as change the creation and last modified time stamps of those elements. Combined with the fact that storages also have their own access rights (read-only, read/write, and so forth), a storage is more powerful than a command shell and more powerful than traditional file I/O libraries.
Istorage | Command-Line | Description |
Release (last reference count only) | (none) | Closes the storage element. If the storage is a root object attached to a file, Release closes the file and can optionally delete it. If the storage is transacted, Release discards changes. (See also Revert.) |
CreateStream | Copy | Creates and opens a stream within the storage, returning an IStream pointer. |
OpenStream | Copy, Type | Opens an existing stream within the storage, returning an IStream pointer. |
CreateStorage | Mkdir, Chdir | Creates and opens a new substorage within the storage, returning an IStorage pointer. |
OpenStorage | Chdir | Opens an existing substorage within the storage, returning an IStorage pointer. |
CopyTo | Copy | Copies the entire contents from the storage into another storage, removing excess unused space in the process. |
Commit | (none) | Ensures that all changes made to a storage are reflected to the parent storage (or to the device itself in the case of a root storage); also flushes buffers. |
Revert | (none) | Discards any changes made to a transacted storage since the last Commit. |
EnumElements | Dir | Returns a STATSTG enumerator object (implementing IEnumSTATSTG), which enumerates information relating to the substorages and streams within the storage. |
MoveElementTo | Copy (+Del) | Copies or moves a substorage or a stream from the storage into another storage. |
DestroyElement | Del, Deltree | Removes a specified substorage or stream from within the storage. If a substorage is destroyed, all elements contained within it are also destroyed. |
RenameElement | Rename | Changes the name of a stream or a substorage. |
SetElementTimes | (none) | Sets the modification, last access, and creation date and time of a substorage or stream, subject to file-system support. |
SetClass | (none) | Assigns a CLSID to the storage, which can be retrieved by using Stat. The CLSID identifies code associated with the contents of the storage. |
SetStateBits | Attrib | Marks the storage with various flags. |
Stat | (varies) | Retrieves a STATSTG structure describing storage statistics and attributes. |
Table 7-1.
The IStorage interface.
It is important to realize that all storage objects that implement IStorage are the same as far as the interface is concerned, regardless of whether they are a substorage deep in a hierarchy or a root storage attached to the underlying file system or storage medium itself. So, for example, the MoveElementTo member can move or copy a stream between storages in different points in the same storage hierarchy or between storages in different hierarchies. CopyTo can copy all the elements under a root storage to a substorage in some other hierarchy. In other words, all storages are peers without regard to their position within a hierarchy.
As we can see from Table 7-1, IStorage::CreateStorage and IStorage::OpenStorage return an IStorage pointer, and these functions are the only means to obtain such a pointer to a substorage anywhere in a hierarchy. This is another example of the third way to obtain an interface pointer for a new object, which we described in Chapter 2. But how does one obtain the pointer to the root storage object? That depends on the implementation of Structured Storage, and as we'll see a little later in this chapter, OLE's Compound Files provide a few API functions through which you obtain that first IStorage pointer. But let's first see how a stream works.