Delegating to the Default Handler and Data Cache

We programmers generally do not like to make extra work for ourselves—if code has already been written somewhere, we use it. I'm not saying we're lazy; we simply like to be as resourceful as possible in finding reusable code. That's why a language such as C++ was invented in the first place and why OLE has the code-reuse mechanisms of containment and aggregation.

In writing handlers and in-process servers, there are a lot of interfaces to implement: IOleObject, IDataObject, IPersistStorage, IRunnableObject, IOleCache2, and possibly IOleCacheControl. Many of these, however, you can obtain through aggregation with the default handler or the data cache. A handler always aggregates on the default handler by calling OleCreateDefaultHandler (which takes arguments similar to CoCreateInstance), exposing many of the default handler's interfaces directly through aggregation and delegating other selected calls through containment as necessary. An in-process server aggregates on the data cache through CreateDataCache, again exposing some of its interfaces directly and delegating to others. The in-process server generally implements IOleObject and IRunnableObject by itself, exposes the caching interfaces directly, and delegates a good number of calls to the other three interfaces to the cache's implementation, especially for display aspects that the server doesn't want to manage directly.

We've already explored the behavior of the data cache in Chapter 11, where we articulated the specific functionality of its IDataObject, IPersistStorage, IViewObject2, IOleCache2, and IOleCacheControl interfaces. (Please refer to Tables 11-1 through 11-4 on pages 552 through 556 for a refresher on the data cache.) Remember that the data cache will never attempt to launch a local server itself. That's not part of its design! Nor does an in-process server ever need a local server, of course.

Running the local server is, however, part of the design of the default handler. To understand how we can make use of the handler we need to see how it behaves through all of these interfaces as well as through IOleObject. In general, an object handler will directly expose the default handler's IDataObject, IRunnableObject, IOleCache2, and IOleCacheControl interfaces (and possibly also IOleObject and IPersistStorage) through aggregation. Handlers designed for the express purpose of display and printing optimization will generally implement only portions of IViewObject2 themselves and then delegate the remainder of that interface to the default handler. We'll see an example later in this chapter.

The default handler itself uses the data cache internally. In fact, the default handler generally exposes the cache's IPersistStorage, IViewObject2, IOleCache2, and IOleCacheControl interfaces. For IPersistStorage, the default handler will delegate the call to a running object and then to the cache; otherwise, it just delegates to the cache. In either case, the behavior of these interfaces through the default handler is identical to the behavior of the cache. That leaves us to examine IOleObject and IDataObject (which are covered in the next two sections) and IRunnableObject. This last interface has a simple implementation: if the object isn't running, calling IRunnableObject::Run will launch the server but all other members will fail. (IsRunning will return S_FALSE.) If the object is running, Run and IsRunning will succeed and other calls are forwarded to the object.