Notes on Implementing an In-Process Server

In Chapter 18, we saw how to implement a complete local server, and in the last section we saw how to implement an in-process handler. Now we can bring the two together in a single in-process server, as I have done with the Polyline component (CHAP19\POLYLINE). Because we've seen most of the implementation already, this section will highlight specific issues that I faced when modifying this sample. When implementing an in-process server for content objects yourself, be sure to use OLE's caching services through CreateDataCache and not through OleCreateDefaultHandler.

Let me point out that modifications made to Polyline for OLE Documents have not affected its usefulness to Component Cosmo (last seen in Chapter 13). In fact, the only change necessary for CoCosmo is to have it use CLSID_Polyline19 instead of CLSID_Polyline10, as it has been doing in CCosmoDoc::Init.3 If I hadn't changed Polyline's CLSID, CoCosmo would require no changes at all! This shows that support for OLE Documents does not interfere with the general operation of the object as a general purpose component. Although a container can now use Polyline's embedded objects, CoCosmo can still use exactly the same in-process server as before. That's the beauty of the QueryInterface mechanism and interface separation.

Much of Polyline's implementation is a cross between the code we've seen in this chapter for HCosmo and the code we saw in the last chapter for Cosmo itself. One of the more interesting points to note is that because Polyline cannot run stand-alone, it really can't create the same user interface as Cosmo does for editing a figure. I originally set out to do just this, but I soon discovered that it is difficult to manage a top-level pop-up window with a menu, a toolbar, and document windows within a DLL. For that reason, Polyline uses a dialog box–style user interface to display its figure from IOleObject::DoVerb, as we'll see in a moment. First let's look briefly at how this version of Polyline differs from the one we saw in Chapter 10:

These last two changes are most important because they deal with the special needs of activating an in-process object. Again, Polyline uses a dialog in which to display its figure because of the difficulty of creating a normal application user interface from within a DLL. This is actually a good thing—it doesn't make sense to display a user interface that's part of a stand-alone application for a module that cannot run stand-alone at all.

The better user interface for an in-process server is a dialog box, which makes your object look a lot like a part of the container application. Because you are invoking this dialog box from a DLL that has already been loaded, a window such as this will appear quite quickly after the end user activates the object in the container. This further reinforces the idea that the dialog box is tightly integrated with the container. Dialog boxes also give you quite a bit of support, such as keyboard mnemonics for controls, that you would not otherwise get without your own message loop.

Polyline actually creates its dialog box, shown in Figure 19-2 on the next page, with CreateDialogParam inside IRunnableObject::Run, which will be called whenever the object enters at least the running state. At this time, Polyline can create a figure window within that dialog and give the figure a definite size so that implementations of IViewObject2::GetExtent and IOleObject::GetExtent can return something meaningful.

Figure 19-2.

Polyline's dialog box user interface for editing an embedded object.

IOleObject::DoVerb then needs only to make this window visible or invisible, depending on the verb. DoVerb will create the dialog by calling IRunnableObject::Run if for some reason that dialog is not yet active or has been closed. A call to IOleObject::Close will destroy the dialog box itself, although the Polyline object remains loaded.

When we make the dialog visible, we also center it on the screen instead of letting it be placed in relation to the container window. This is because the default placement of a dialog box will typically cover the upper left portion of the container's client area, just where a site is usually found. The dialog box covers the object in the document. When I had Polyline working in this way, I found that the first thing I did after activating the object was move the dialog box out of the way. I expect other users would be frustrated by the same thing. Centering the dialog on the screen will generally keep the site visible, your server more usable, and your customers more satisfied.

Other than that, the dialog box processes commands the same way any other dialog box does, changing Polyline's line style or invoking the Choose Color dialog box to change background and line colors. It also sends the appropriate notifications when closing the dialog box, as we would when closing a document window in a server application. We don't need to call IOleClientSite::SaveObject because we aren't unloading the server; we're simply closing the dialog box. We had to do this in a local server because the application would generally shut down when the user interface went away, and that would mean the data was lost as well. But because this is an in-process server, it's going to stay in memory along with the object's most current data, data that will be used in subsequent calls to IViewObject2::Draw and the like. When the container wants to actually save the object, it calls OleSave, which will call our IPersistStorage::Save, in which we'll save our current data. Therefore, no SaveObject call is necessary.

3 The CHAP19\COCOSMO directory contains a README.TXT describing the changes necessary to make a new version of CoCosmo.