The Basic OLE Documents Architecture

Whenever any object is in the loaded, running, or active state, a client or a container has at least one interface pointer for that object. To have this pointer, some in-process piece of code must be loaded for that object. The code can be the object server itself, a handler, or simply a proxy. In OLE Documents, the object exposes at least those interfaces shown in Figure 17-2 on page 819: IOleObject, IDataObject, IPersistStorage, IViewObject2, IRunnableObject, IOleCache2, and, optionally, IOleCacheControl. This is not to say that any embedded object implementation must manually implement all of these interfaces directly. There are, in fact, three basic ways to create the proper in-process objects:

Regardless of the technique in use, the container always sees the same interfaces. Many of these interfaces are strictly in-process or container-side interfaces—namely, IViewObject2, IOleCache2, IOleCacheControl, and IRunnableObject. Accordingly, a content object implemented in a local server does not bother with these interfaces; the object implements only IOleObject, IDataObject, and IPersistStorage. IDataObject exists mostly so that the container-side cache can retrieve presentations. This process is illustrated in Figure 17-3.

The presence of IPersistStorage means that each embedded (or linked) object, regardless of what sort of server implements it, requires its own storage element in the container's underlying file. It also means that the object will maintain incremental access to this storage unless told otherwise through various IPersistStorage members. Obviously, the easy way to provide individual storage elements is for the container to use a compound file, as discussed in Chapter 7, but this is not strictly required. Using ILockBytes and the functions StgCreateDocfileOnILockBytes and StgOpenStorageOnILockBytes, a container can wrap individual pieces of its own storage medium inside IStorage pointers. The content object doesn't know the difference. Thus, a container can have objects read and write to a piece of some private file, to a database record, to a piece of global memory, and so on. When implementing a container, one of your first steps should be to define exactly how you will provide a separate IStorage pointer to each content object.

A major benefit of using a compound file as the compound document is that objects in both in-process handlers and local servers can simultaneously maintain incremental access to the object's storage while the container itself maintains access to the rest of the file. This relationship also is shown in Figure 17-3.

Figure 17-3.

The container, the cache, and the objects inside in-process handlers and local servers can all access the object's storage simultaneously as needs arise.

The container itself can store custom streams inside the object's storage as long as those streams are named with an ASCII 3 prefix. As described in Chapter 7, this naming convention marks the stream as "container owned" so that the content object as well as OLE will never touch or mangle the stream in any way. OLE itself uses other specifically named streams to store its own information in the object's storage, as shown in the following table:

Stream Name

Contents

\001CompObj

The CLSID of the object written with WriteClassStg. ReadClassStg opens this stream to retrieve the CLSID.

\001Ole

Contains information about the object, such as whether it's linked or embedded.

\002OlePres000

The primary cached presentation for this object. If there are no cached presentations, this stream will not be present.

\002OlePresnnn

Additional cached presentations.