The Structure of a Local Content Object (and Server)

In Chapter 17, we learned that an object in a local server must implement IOleObject, IDataObject, and IPersistStorage to support its half of the OLE Documents protocol, as illustrated in Figure 18-1. Each of these interfaces has a specific purpose in the overall architecture. IPersistStorage provides for the object's storage of its native data, allowing that object incremental access. Because the container will pass nothing more than an IStorage pointer through this interface, the object does not load data until absolutely necessary. Keep in mind that a local object deals only with its own native data in storage because all other information that might be present is owned either by the container or by OLE itself. In all cases, those streams have special names and are relevant to the container process only.

Figure 18-1.

The structure of a content object in a local server.

IDataObject, as we know from Chapter 17 as well as from the discussion of caching in Chapter 11, is the interface the container-side data cache uses to retrieve from the object presentation formats such as bitmaps and metafiles. Through this interface, the local object must supply these formats in addition to its own native formats and the standard OLE formats CFSTR_EMBEDSOURCE and CFSTR_EMBEDDEDOBJECT. (Also linked object formats, as we'll see in Chapter 21.) Containers can also retrieve other specific data formats through IDataObject if they know more about the object class itself.

The third interface is IOleObject, which, as we saw in Chapter 17, makes an object a content object. For the most part, many of the members in this interface need little or no implementation for basic embedding support, and some of them are utterly trivial to implement. The most important of all members is, of course, IOleObject::DoVerb.

Like all other servers, a server that works with OLE Documents must provide a class factory and registry entries that map its CLSID to the server code. In Chapter 17, we saw the additional registry entries that are necessary for OLE Documents. Also, the inclusion of an active state for the object means that the user interface might be visible, in which case server shutdown must be controlled not only with an object count and a factory lock count but also with a user control flag, especially if the server is able to support both multiple objects and independent documents at the same time. The last section of this chapter discusses some considerations regarding MDI (Multiple Document Interface) servers.

Besides implementing the necessary interfaces on the object, a local server must also modify its own user interface in a few small ways when it's working with an embedded object. In particular, because the object is not a file, the server must remove file-related user interface elements from its menus and toolbars and change its caption bar to reflect the name of the compound document itself (which the container passes to IOleObject::SetHostNames). In addition, the object is required to send a number of notifications for events that occur during its active lifetime, particularly notifications contained in IOleClientSite and IAdviseSink, as you would expect.

Other than that, the server should also be able to create embedded objects for clipboard and drag-and-drop operations, supplying the embedded object formats along with CFSTR_OBJECTDESCRIPTOR. The server can also choose to install a message filter to block or delay calls if it needs to do so.(Chapter 6 discusses the details of message filtering for a server.)

In designing a content object server, you'll need to think about what it is that your class factory will create. What is the embedded object inside your server? Is it simply a C++ wrapper class that communicates with other parts of the application, or is it some existing object class that you'll augment with the necessary interfaces? Either case is perfectly fine. What matters is that you provide the appropriate interfaces when they're asked for. In Cosmo, I've added a new C++ class named CFigure, which manages all the stuff related to OLE Documents, leaving almost all of the existing CPolyline class untouched. This demonstrates how you can encapsulate any sort of legacy code in an object and have it appear as a content object to the outside world.

Now, before we dive into Cosmo's implementation, let's consider three other issues that deserve a little thought.