Container_Component Interactions

Analyzing the component and the container separately won't help you to understand fully how they work. You must watch them working together to understand their interactions. Let's reveal the complexity one step at a time. Consider first that you have a container EXE and a component EXE, and the container must manage the component by means of OLE interfaces.

Look back to the space simulation example in Chapter 24. The client program called CoGetClassObject and IClassFactory::CreateInstance to load the spaceship component and to create a spaceship object, and then it called QueryInterface to get IMotion and IVisual pointers. An embedding container program works the same way that the space simulation client works. It starts the component program based on the component's class ID, and the component program constructs an object. Only the interfaces are different.

Figure 28-2 shows a container program looking at a component. You've already seen all the interfaces except one—IOleObject.

click to view at full size.

Figure 28-2. A container program's view of the component.

Using the Component's IOleObject Interface

Loading a component is not the same as activating it. Loading merely starts a process, which then sits waiting for further instructions. If the container gets an IOleObject pointer to the component object, it can call the DoVerb member function with a verb parameter such as OLEIVERB_SHOW. The component should then show its main window and act like a Windows-based program. If you look at the IOleObject::DoVerb description, you'll see an IOleClientSite* parameter. We'll consider client sites shortly, but for now you can simply set the parameter to NULL and most components will work okay.

Another important IOleObject function, Close, is useful at this stage. As you might expect, the container calls Close when it wants to terminate the component program. If the component process is currently servicing one embedded object (as is the case with MFC components), the process exits.

Loading and Saving the Component's Native Data—Compound Documents

Figure 28-2 demonstrates that the container manages a storage through an IStorage pointer and that the component implements IPersistStorage. That means that the component can load and save its native data when the container calls the Load and Save functions of IPersistStorage. You've seen the IStorage and IPersistStorage interfaces used in Chapter 27, but this time the container is going to save the component's class ID in the storage. The container can read the class ID from the storage and use it to start the component program prior to calling IPersistStorage::Load.

Actually, the storage is very important to the embedded object. Just as a virus needs to live in a cell, an embedded object needs to live in a storage. The storage must always be available because the object is constantly loading and saving itself and reading and writing temporary data.

A compound document appears at the bottom of Figure 28-2. The container manages the whole file, but the embedded components are responsible for the storages inside it. There's one main storage for each embedded object, and the container doesn't know or care what's inside those storages.

Clipboard Data Transfers

If you've run any OLE container programs, including Microsoft Excel, you've noticed that you can copy and paste whole embedded objects. There's a special data object format, CF_EMBEDDEDOBJECT, for embedded objects. If you put an IDataObject pointer on the clipboard and that data object contains the CF_EMBEDDEDOBJECT format (and the companion CF_OBJECTDESCRIPTOR format), another program can load the proper component program and reconstruct the object.

There's actually less here than meets the eye. The only thing inside the CF_EMBEDDEDOBJECT format is an IStorage pointer. The clipboard copy program verifies that IPersistStorage::Save has been called to save the embedded object's data in the storage, and then it passes off the IStorage pointer in a data object. The clipboard paste program gets the class ID from the source storage, loads the component program, and then calls IPersistStorage::Load to load the data from the source storage.

The data objects for the clipboard are generated as needed by the container program. The component's IDataObject interface isn't used for transferring the objects' native data.

Getting the Component's Metafile

You already know that a component program is supposed to draw in a metafile and that a container is supposed to play it. But how does the component deliver the metafile? That's what the IDataObject interface, shown in Figure 28-2, is for. The container calls IDataObject::GetData, asking for a CF_METAFILEPICT format. But wait a minute. The container is supposed to get the metafile even if the component program isn't running. So now you're ready for the next complexity level.

The Role of the In-Process Handler

If the component program is running, it's in a separate process. Sometimes it's not running at all. In either case, the OLE32 DLL is linked into the container's process. This DLL is known as the object handler.

It's possible for an EXE component to have its own custom handler DLL, but most components use the "default" OLE32 DLL.

Figure 28-3 shows the new picture. The handler communicates with the component over the RPC link, marshaling all interface function calls. But the handler does more than act as the component's proxy for marshaling; it maintains a cache that contains the component object's metafile. The handler saves and loads the cache to and from storage, and it can fill the cache by calling the component's IDataObject::GetData function.

When the container wants to draw the metafile, it doesn't do the drawing itself; instead, it asks the handler to draw the metafile by calling the handler's IViewObject2::Draw function. The handler tries to satisfy as many container requests as it can without bothering the component program. If the handler needs to call a component function, it takes care of loading the component program if it is not already loaded.

The IViewObject2 interface is an example of OLE's design evolution. Someone decided to add a new function—in this case, GetExtent—to the IViewObject interface. IViewObject2 is derived from IViewObject and contains the new function. All new components should implement the new interface and should return an IViewObject2 pointer when QueryInterface is called for either IID_IViewObject or IID_IViewObject2. This is easy with the MFC library because you write two interface map entries that link to the same nested class.

click to view at full size.

Figure 28-3. The in-process handler and the component.

Figure 28-3 shows both object data and metafile data in the object's storage. When the container calls the handler's IPersistStorage::Save function, the handler writes the cache (containing the metafile) to the storage and then calls the component's IPersistStorage::Save function, which writes the object's native data to the same storage. The reverse happens when the object is loaded.



Programming Visual C++
Advanced 3ds max 5 Modeling & Animating
ISBN: 1572318570
EAN: 2147483647
Year: 1997
Pages: 331
Authors: Boris Kulagin

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net