Stream Objects and the IStream Interface

As we've seen already, in Structured Storage a stream is the equivalent of a file in which you can store any bits you want. Like a file, a stream maintains its own seek pointer. But instead of reading and writing through a file handle, you work with an IStream interface pointer, as described in Table 7-2. Through this pointer, a client always views the stream as a contiguous byte array; the actual data, however, might be spread around within the storage medium.

The SetSize function is included in IStream as an optimization allowing a client to preallocate space in the stream. Usually a stream will allocate more space if a Write operation extends past the current end of the stream. However, this allocation might fail, so SetSize allows the client to preallocate space and handle errors outside the process of writing data. Note, however, that SetSize, like the CopyTo function, is often slow. (CopyTo, for example, has to deal with potentially overlapping streams.) In contrast, the most common operations—performed by Read, Write, and Seek—are usually optimized as much as possible to provide the best performance most of the time.

As shown in Table 7-2, many stream functions equate to existing file functions. The practical upshot of this is that most code written to use files through _lread and _lwrite are easily rewritten to work with IStream::Read and IStream::Write. Streams also have the same access rights as files do (read-only, read/write, share-exclusive, and so forth), as well as their own seek pointer. One advantage of a stream over a file, however, is that you can open as many streams as you want without hogging a lot of file handles: only the root storage of a structured storage file requires a file handle—everything else is simply a memory structure. Because of this efficiency, you might find many new ways in which multiple streams can improve an application's storage architecture that were simply too expensive with file handles.

Istream
Member

File Equivalent*

Description

Release (last reference count only)

_lclose

Closes the stream, discarding changes if the stream is transacted (equivalent of Revert).

Read

_lread

Reads into memory a given number of bytes from the current seek offset.

Write

_lwrite

Writes a number of bytes from memory to the stream, starting at the current seek offset.

Seek

_llseek

Moves the seek offset to a new position from the beginning of the stream, from the end of the stream, or from the current position.

SetSize

_chsize

Preallocates space for the stream but does not preclude writing outside that stream. (See the discussion that follows.)

CopyTo

memcpy

Copies the number of bytes from the current seek offset in the stream either to the current seek offset in another stream or to the one in a clone of the same stream.

Commit

(none)

Publishes all changes to the parent storage and flushes all internal buffers.

Revert

(none)

Discards any changes made to a transacted stream since the last Commit.

LockRegion

_locking

Restricts access to a byte range in the stream instead of the stream as a whole.

UnlockRegion

_locking

Frees restrictions set with LockRegion.

Stat

_stat

Retrieves a STATSTG structure describing stream statistics and attributes.

Clone

_dup

Creates a new stream object that works with the same actual bytes but manages an independent seek offset. This allows you to access different parts of the same stream with multiple clones rather than work with a single seek offset.


* Both Windows API and C run-time functions are shown here.

Table 7-2.

The IStream interface.

You will notice that IStream, unlike IStorage, has no functions to deal with any other structure within the stream: Read and Write simply expose a byte array. To obtain a pointer to a stream within a storage hierarchy, you call either IStorage::CreateStream or IStorage::OpenStream; the stream always ends a branch in the hierarchy.

Streams on Memory

As we saw in the discussion of custom marshaling in Chapter 6, OLE provides a stand-alone stream object that works on a piece of global memory. The function CreateStreamOnHGlobal will construct such a stream on any memory block you give it, returning an IStream pointer that you can use in the same way as an IStream pointer in some other storage hierarchy. Memory streams can be extremely useful for tasks such as making a memory snapshot of a stream within a storage hierarchy. In other cases, it is convenient to work with memory through an IStream pointer instead of through a BYTE * pointer—a stream is a higher-level abstraction than an array of bytes.

CreateStreamOnHGlobal takes a Boolean argument indicating whether the stream is to take control of the underlying memory such that the last IStream::Release call will free the memory in addition to freeing the stream itself. This means that the client of a memory stream needs only to hold an IStream pointer instead of the pointer and the HGLOBAL underneath it. In other words, the following code has no memory leaks because GlobalFree is contained inside IStream::Release:


IStream    *pIStream;
HGLOBAL hMem;

hMem=GlobalAlloc(...);
CreateIStreamOnHGlobal(hMem, &pIStream, TRUE);
[Use stream as long as necessary.]
pIStream->Release(); //Memory freed.

The function GetHGlobalFromStream exists in case you need to retrieve the memory handle from the stream at some later time. Even if the client retains control of the memory, it still doesn't have to hold the handle itself: the stream holds the handle regardless of ownership.

Streams on Existing Files

Because streams and files are so close in nature, it seems natural that you should be able to create a stream object that accesses a file through IStream instead of through a file handle. OLE doesn't currently provide a simple API function for this exact purpose, although for small files you can easily load the file into memory and create a stream on that memory.

Structured Storage does, however, define a special access mode that allows a client to open an existing file as if it were a root storage, and the contents of the existing file are exposed through a stream named CONTENTS (big surprise). So while you can't simply open a stream on top of a file, you can open a root storage and then open a stream.