The Structured Storage model doesn't make any stipulation about the storage device that is hidden behind the root storage object. OLE's compound files use an object named LockBytes to abstract any storage device into a generic byte array. This object implements ILockBytes, which supports operations such as "read so many bytes from this part of the array" and "write these bytes at this address." A LockBytes object doesn't know what information is being placed in the array; it simply makes any potentially noncontiguous storage medium appear as a long flat byte array, exactly the same way a file system presents a file and just the way a stream looks to its client. In short, a LockBytes object acts like a sort of device driver, isolating the rest of the compound files implementation from any specific knowledge about the device, as illustrated in Figure 7-7.
With compound files, OLE provides a default LockBytes implementation that works with a handle-based file from the local file system. The API functions that we'll discuss install this LockBytes object as part of their functionality, but you can implement your own object with ILockBytes and create a compound file on top of it. Or you can use a standard OLE-provided implementation that works on global memory. In both of these cases, an alternative LockBytes is a customization of OLE's service that gives you control over where the bits finally end up.
Figure 7-7.
A LockBytes object sits on a device, a root storage builds on the LockBytes object, and streams and storages live below that root storage.
The global-memory LockBytes implementation is provided through the API function CreateILockBytesOnHGlobal, to which you can pass a section of global memory or let the function allocate the memory itself. You can tell the LockBytes object to free the memory automatically when the object itself is destroyed. The function GetHGlobalFromILockBytes then returns the global-memory handle underneath the object. This allows you to manage the LockBytes object as you would a memory stream obtained through CreateStreamOnHGlobal—you need only to maintain a pointer, retrieving the memory from the object if you need it. In the future, you can expect to see functions to create a LockBytes object on top of a traditional file, just like the APIs that would allow you to open a stream on top of a file.
Although an implementation of ILockBytes lets you control where the bits are stored, it does not give you the ability to determine what is contained in those bits. As we can see in Table 7-5 on the following page, the ILockBytes member functions ReadAt and WriteAt must blindly read or write blocks of data without any interpretation of what those blocks contain. These read and write mechanisms are similar to those in a stream, but a LockBytes object maintains no seek offset—it is always told where to read and where to write in every call. So although a LockBytes object controls the physical location of bits, which need not be contiguous (they might span multiple physical files, multiple global-memory allocations, multiple database fields, and so forth), OLE retains control of the compound file data structures. The LockBytes object isolates OLE from the physical aspects of the storage device, presenting that device as a contiguous byte array.
ILockBytes | Member |
ReadAt | Reads a number of bytes from a given location in the byte array. If there are not enough bytes on the device to satisfy the request, ReadAt returns as many bytes as can be read. |
WriteAt | Writes a number of bytes to a given location in the byte array, expanding the allocations on the device to accommodate the request. |
Flush | Ensures that any internal buffers are written to the device. |
SetSize | Preallocates a specific amount of space on the device. |
LockRegion | Locks a range of bytes on the device for write access or exclusive access. |
UnlockRegion | Reverses a LockRegion call. |
Stat | Fills a STATSTG structure with information about the object, which in turn reflects information about the device. |
Table 7-5.
The ILockBytes interface.