Storage Objects and the IStorage Interface

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
Member

Command-Line
Equivalent

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.