Structured storage refers to the hierarchical organization of storage introduced with OLE. In a structured storage environment, storage is organized into two or three types of objects:
Stream and lock bytes are lower-level objects that directly access the data. Stream objects implement the IStream interface which defines methods for reading, writing, positioning, and copying bytes of data. Lock bytes objects implement another OLE interface, ILockBytes, to access data with a byte array. Byte arrays are typically used to provide customized access to underlying storage.
Storage objects are layered on top of the stream or lock bytes objects; they can contain one or more of these objects as well as other storage objects. Storage objects implement the IStorage interface which defines methods for creating, accessing, and maintaining nested objects.
Because IStream, ILockBytes, and IStorage are OLE interfaces rather than MAPI interfaces, their methods return OLE error values rather than MAPI values. Clients and service providers calling methods in these interfaces must use the API function MapStorageSCode to translate these values into MAPI error values.
Clients and service providers use structured storage for working with properties that are too large to maintain with the IMAPIProp methods, typically large string and binary properties. One of the common ways that clients or service providers access them is by specifying IStream or IStorage as the interface identifier in a call to the IMAPIProp::OpenProperty method. For example, clients call OpenProperty with PR_ATTACH_DATA_BIN as the property tag and IID_IStream as the interface identifier to access a binary attachment in a message.
Clients and service providers can implement their own stream and storage objects or call API functions to access implementations supplied by MAPI or OLE. Because the supplied implementations serve most purposes, clients and service providers rarely need to create their own.
When a client calls OpenProperty on a MAPI object to access one of its properties through a storage object, the service provider will typically open the storage object in direct mode. However, this is typical rather than required behavior. Clients should assume that all storage objects opened or created by service providers are transacted and require a call to IStorage::Commit. They should also remember that changes to storage objects will not be made permanent until they call IMAPIProp::SaveChanges after the final Commit to save the MAPI object.
MAPI and OLE provide several API functions for defining or accessing storage and stream objects. The commonly used functions are described in the following table.
Function | Description |
---|---|
CreateDocfile | Creates a general purpose storage object. |
HrIStorageFromStream | Creates a storage object to access a stream or lock bytes object. |
OpenIMsgOnIStg | Creates a message object to access a storage object. |
OpenStreamOnFile | Creates a stream object to access a file. |
WrapCompressedRTFStream | Creates a stream object that contains the compressed or uncompressed version of a stream holding the rich text of a message. |
CreateDocfile is used frequently in OLE and in MAPI to create a storage object. For more information on this function, refer to the OLE documentation.
An optional input parameter to OpenIMsgOnIStg is a callback function that conforms to the MSGCALLRELEASE prototype. This function is called by the new message object when the message's reference count reaches zero. Implementing a MSGCALLRELEASE function can be useful for performing final processing before the new message is completely removed.
OpenStreamOnFile is commonly used for storing file attachments because it creates a stream that reads from and writes to a file. OpenProperty with PR_ATTACH_DATA_BIN as the property tag creates a stream for storing binary attachment data.
Seek
SetSize
Revert
LockRegion
UnlockRegion
Stat
Clone
Because the stream objects created by WrapCompressedRTFStream do not support either SetSize or Stat, there is not an easy way to either extend or retrieve their size. The best strategy is to pick a reasonable buffer size and read or write in a loop.
Note OLE has a storage object implementation based on a byte array that returns an IEnumSTATSTG object from the EnumElements method that is problematic. In particular, the QueryInterface method does not work correctly. Service providers that implement their own storage objects using the OLE implementation should create a thin wrapper for the IEnumSTATSTG object that forwards calls on to the underlying IEnumSTATSTG methods but implements its own AddRef, Release, QueryInterface, and Clone methods.