| Tenouk C & C++ | MFC Home | OLE & Containers 1 | OLE & Containers 3 | Download | Site Index |


 

 

 

 

 

 

OLE Embedded Components and Containers part 2

 

 

 

 

 

Program examples compiled using Visual C++ 6.0 compiler on Windows XP Pro machine with Service Pack 2. The Excel version is Excel 2003/Office 11. Topics and sub topics for this tutorial are listed below. Don’t forget to read Tenouk’s small disclaimer. The supplementary notes for this tutorial are IOleObject and OLE.

  1. An MDI Embedded Component?

  2. In-Place Component Sizing Strategy

  3. Container-Component Interactions

  4. Using the Component's IOleObject Interface

  5. Loading and Saving the Component's Native Data: Compound Documents

  6. Clipboard Data Transfers

  7. Getting the Component's Metafile

  8. The Role of the In-Process Handler

  9. Component States

  10. The Container Interfaces

  11. The Advisory Connection

  12. A Metafile for the Clipboard

  13. An Interface Summary

  14. The IOleObject Interface

  15. When to Implement

  16. When to Use

  17. The IViewObject2 Interface

  18. When to Implement

  19. When to Use

  20. The IOleClientSite Interface

  21. The IAdviseSink Interface

  22. When to Implement

  23. When to Use

  24. OLE Helper Functions

  25. An OLE Embedding Container Application

  26. MFC Support for OLE Containers

  27. Some Container Limitations

  28. Container Features

 

An MDI Embedded Component?

 

The EX32A example is an SDI mini-server. Each time a controller creates an EX32A object, a new EX32A process is started. You might expect an MDI mini-server process to support multiple component objects, each with its own document, but this is not the case. When you ask AppWizard to generate an MDI mini-server, it generates an SDI program, as in EX32A. It's theoretically possible to have a single process support multiple embedded objects in different windows, but you can't easily create such a program with the MFC library.

 

In-Place Component Sizing Strategy

 

If you look at the EX32A output, you'll observe that the metafile image does not always match the image in the in-place frame window. We had hoped to create another example in which the two images matched. We were unsuccessful, however, when we tried to use the Microsoft Office 97 applications as containers. Each one did something a little different and unpredictable. A complicating factor is the containers' different zooming abilities.

When AppWizard generates a component program, it gives you an overridden OnGetExtent() function in your server item class. This function returns a hard-coded size of (3000, 3000). You can certainly change this value to suit your needs, but be careful if you change it dynamically. We tried maintaining our own document data member for the component's extent, but that messed us up when the container's zoom factor changed. We thought containers would make more use of another component item virtual function, OnSetExtent(), but they don't. You'll be safest if you simply make your component extents fixed and assume that the container will do the right thing. Keep in mind that when the container application prints its document, it prints the component metafiles. The metafiles are more important than the in-place views. If you control both container and component programs, however, you have more flexibility. You can build up a modular document processing system with its own sizing protocol. You can even use other OLE interfaces.

 

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 Module 23. 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 1 shows a container program looking at a component. You've already seen all the interfaces except one, IOleObject.

 

 

Figure 1:  A container program's view of the component.

 

Figure 1:  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 1 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 Module 26, 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 1. 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 1, 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 2 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.

 

Figure 2:  The in-process handler and the component.

 

Figure 2:  The in-process handler and the component.

 

Figure 2 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.

 

Component States

 

Now that you know what a handler is, you're ready for a description of the four states that an embedded object can assume.

 

State

Description

Passive

The object exists only in a storage.

Loaded

The object handler is running and has a metafile in its cache, but the EXE component program is not running.

Running

The EXE component program is loaded and running, but the window is not visible to the user.

Active

The EXE component's window is visible to the user.

 

Table 1.

 

The Container Interfaces

 

Now for the container side of the conversation. Look at Figure 3. The container consists of a document and one or more sites. The IOleContainer interface has functions for iterating over the sites, but we won't worry about iterating over the client sites here. The important interface is IOleClientSite. Each site is an object that the component accesses through an IOleClientSite pointer. When the container creates an embedded object, it calls IOleObject::SetClientSite to establish one of the two connections from component to container. The site maintains an IOleObject pointer to its component object.

One important IOleClientSite function is SaveObject(). When the component decides it's time to save itself to its storage, it doesn't do so directly; instead, it asks the site to do the job by calling IOleClientSite::SaveObject. "Why the indirection?" you ask. The handler needs to save the metafile to the storage, that's why. The SaveObject() function calls IPersistStorage::Save at the handler level, so the handler can do its job before calling the component's Save() function.

Another important IOleClientSite function is OnShowWindow(). The component program calls this function when it starts running and when it stops running. The client is supposed to display a hatched pattern in the embedded object's rectangle when the component program is running or active.

 

Figure 3: The interaction between the container and the component.

 

Figure 3: The interaction between the container and the component.

 

The Advisory Connection

 

Figure 3 shows another interface attached to the site, IAdviseSink. This is the container's end of the second component connection. Why have another connection? The IOleClientSite connection goes directly from the component to the container, but the IAdviseSink connection is routed through the handler. After the site has created the embedded object, it calls IViewObject2::SetAdvise, passing its IAdviseSink pointer. Meanwhile, the handler has gone ahead and established two advisory connections to the component. When the embedded object is created, the handler calls IOleObject::Advise and then calls IDataObject::DAdvise to notify the advise sink of changes in the data object. When the component's data changes, it notifies the handler through the IDataObject advisory connection. When the user saves the component's data or closes the program, the component notifies the handler through the IOleObject advisory connection. Figure 4 shows these connections.

When the handler gets the notification that the component's data has changed (the component calls IAdviseSink::OnDataChange), it can notify the container by calling IAdviseSink::OnViewChange. The container responds by calling IViewObject2::Draw in the handler. If the component program is not running, the handler draws its metafile from the cache. If the component program is running, the handler calls the component's IDataObject::GetData function to get the latest metafile, which it draws. The OnClose() and OnSave() notifications are passed in a similar manner.

 

Figure 4: Advisory connection details.

 

Figure 4: Advisory connection details.

 

 

A Metafile for the Clipboard

 

As you've just learned, the container doesn't deal with the metafile directly when it wants to draw the embedded object; instead, it calls IViewObject2::Draw. In one case, however, the container needs direct access to the metafile. When the container copies an embedded object to the clipboard, it must copy a metafile in addition to the embedded object and the object descriptor. That's what the handler's IDataObject interface is for. The container calls IDataObject::GetData, requesting a metafile format, and it copies that format into the clipboard's data object.

 

An Interface Summary

 

Following is a summary of the important OLE interfaces we'll be using in the remaining examples in this module. The function lists are by no means complete, nor are the parameter lists.

 

The IOleObject Interface

 

Embedded components implement this interface. The client site maintains an IOleObject pointer to an embedded object. The IOleObject interface is the principal means by which an embedded object provides basic functionality to, and communicates with, its container.

 

When to Implement

 

An object application must implement this interface, along with at least IDataObject and IPersistStorage, for each type of embedded object that it supports. Although this interface contains 21 methods, only three are nontrivial to implement and must be fully implemented: DoVerb(), SetHostNames(), and Close(). Six of the methods provide optional functionality, which, if not desired, can be implemented to return E_NOTIMPL: SetExtent(), InitFromData(), GetClipboardData(), SetColorScheme(), SetMoniker(), and GetMoniker(). The latter two methods are useful mainly for enabling links to embedded objects.

 

When to Use

 

Call the methods of this interface to enable a container to communicate with an embedded object. A container must call DoVerb() to activate an embedded object, SetHostNames() to communicate the names of the container application and container document, and Close() to move an object from a running to a loaded state. Calls to all other methods are optional. Methods available for IOleObject is listed below.

 

IUnknown Methods

Description

QueryInterface()

Returns pointers to supported interfaces.

AddRef()

Increments reference count.

Release()

Decrements reference count.

 

Table 2.

 

 

IOleObject Methods

Description

SetClientSite()

Informs object of its client site in container.

GetClientSite()

Retrieves object's client site.

SetHostNames()

Communicates names of container application and container document.

Close()

Moves object from running to loaded state.

SetMoniker()

Informs object of its moniker.

GetMoniker()

Retrieves object's moniker.

InitFromData()

Initializes embedded object from selected data.

GetClipboardData()

Retrieves a data transfer object from the Clipboard.

DoVerb()

Invokes object to perform one of its enumerated actions ("verbs").

EnumVerbs()

Enumerates actions ("verbs") for an object.

Update()

Updates an object.

IsUpToDate()

Checks if object is up to date.

GetUserClassID()

Returns an object's class identifier.

GetUserType()

Retrieves object's user-type name.

SetExtent()

Sets extent of object's display area.

GetExtent()

Retrieves extent of object's display area.

Advise()

Establishes advisory connection with object.

Unadvise()

Destroys advisory connection with object.

EnumAdvise()

Enumerates object's advisory connections.

GetMiscStatus()

Retrieves status of object.

SetColorScheme()

Recommends color scheme to object application.

 

Table 3.

 

Some of the important methods are explained below.

 

HRESULT Advise(IAdviseSink* AdvSink, DWORD* pdwConnection);

 

The handler calls this function to establish one of the two advisory connections from the component to the handler. The component usually implements Advise() with an OLE advise holder object, which can manage multiple advisory connections.

 

HRESULT Close(DWORD dwSaveOption);

 

The container calls Close() to terminate the component application but to leave the object in the loaded state. Containers call this function when the user clicks outside an in-place-active component's window. Components that support in-place activation should clean up and terminate.

 

HRESULT DoVerb(LONG iVerb, …, IOleClientSite* pActiveSite, …);

 

Components support numeric verbs as defined in the Registry. A sound component might support a "Play" verb, for example. Embedded components should support the OLEIVERB_SHOW verb, which instructs the object to show itself for editing or viewing. If the component supports in-place activation, this verb starts the Visual Editing process; otherwise, it starts the component program in a window separate from that of its container. The OLEIVERB_OPEN verb causes an in-place-activation-capable component to start in a separate window.

 

HRESULT GetExtent(DWORD dwDrawAspect, SIZEL* pSizel);

 

The component returns the object extent in HIMETRIC dimensions. The container uses these dimensions to size the rectangle for the component's metafile. Sometimes the container uses the extents that are included in the component's metafile picture.

 

HRESULT SetClientSite(IOleClientSite* pClientSite);

 

The container calls SetClientSite() to enable the component to store a pointer back to the site in the container.

 

HRESULT SetExtent(DWORD dwDrawAspect, SIZEL* pSizel);

 

Some containers call this function to impose extents on the component.

 

HRESULT SetHostNames(LPCOLESTR szContainerApp, PCOLESTR szContainerObj);

 

The container calls SetHostNames() so that the component can display the container program's name in its window caption.

 

HRESULT Unadvise(DWORD* dwConnection);

 

This function terminates the advisory connection set up by Advise().

 

The IViewObject2 Interface

 

Embedded component handlers implement this interface. Handlers are a type of COM component for dealing with certain client-side aspects of linking and embedding. The default handler (the one provided by Microsoft) lives in a DLL named "OLE32.DLL." The container calls its functions, but the component program itself doesn't implement them. An IViewObject2 interface cannot be marshaled across a process boundary because it's associated with a device context.

The IViewObject2 interface is an extension to the IViewObject interface which returns the size of the drawing for a given view of an object. You can prevent the object from being run if it isn't already running by calling this method instead of IOleObject::GetExtent. Like the IViewObject interface, IViewObject2 cannot be marshaled to another process. This is because device contexts are only effective in the context of one process. The OLE-provided default implementation provides the size of the object in the cache.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

When to Implement

 

Object handlers and in-process servers that manage their own presentations implement IViewObject2 for use by compound document containers.

 

When to Use

 

A container application or object handler calls the GetExtent() method in the IViewObject2 interface to get the object's size from its cache. Methods available for IViewObject2 are listed below.

 

IUnknown Methods

Description

QueryInterface()

Returns pointers to supported interfaces.

AddRef()

Increments reference count.

Release()

Decrements reference count.

 

Table 4.

 

 

IViewObject Methods

Description

Draw()

Draws a representation of the object onto a device context.

GetColorSet()

Returns the logical palette the object uses for drawing.

Freeze()

Freezes the drawn representation of an object so it will not change until a subsequent Unfreeze().

Unfreeze()

Unfreezes the drawn representation of an object.

SetAdvise()

Sets up a connection between the view object and an advise sink so that the advise sink can receive notifications of changes in the view object.

GetAdvise()

Returns the information on the most recent SetAdvise().

 

Table 5.

 

 

IViewObject2 Method

Description

GetExtent()

Returns the size of the view object from the cache.

 

Table 6.

 

Important methods used in this module are:

 

HRESULT Draw(DWORD dwAspect, …, const LPRECTL lprcBounds, …);

 

The container calls this function to draw the component's metafile in a specified rectangle.

 

HRESULT SetAdvise(DWORD dwAspect, …, IAdviseSink* pAdvSink);

 

The container calls SetAdvise() to set up the advisory connection to the handler, which in turn sets up the advisory connection to the component.

 

The IOleClientSite Interface

 

Containers implement this interface and there is one client site object per component object. The IOleClientSite interface is the primary means by which an embedded object obtains information about the location and extent of its display site, its moniker, its user interface, and other resources provided by its container. An object server calls IOleClientSite to request services from the container. A container must provide one instance of IOleClientSite for every compound-document object it contains. Methods available in IOleClientSite are listed below.

 

IUnknown Methods

Description

QueryInterface()

Returns pointers to supported interfaces.

AddRef()

Increments reference count.

Release()

Decrements reference count.

 

Table 7.

 

 

IOleClientSite Methods

Description

SaveObject()

Saves embedded object.

GetMoniker()

Requests object's moniker.

GetContainer()

Requests pointer to object's container.

ShowObject()

Asks container to display object.

OnShowWindow()

Notifies container when object becomes visible or invisible.

RequestNewObjectLayout()

Asks container to resize display site.

 

Table 8.

 

And the related methods used in this module are:

 

HRESULT GetContainer(IOleContainer** ppContainer);

 

The GetContainer() function retrieves a pointer to the container object (document), which can be used to enumerate the container's sites.

 

HRESULT OnShowWindow(BOOL fShow);

 

The component program calls this function when it switches between the running and the loaded (or active) state. When the object is in the loaded state, the container should display a hatched pattern on the embedded object's rectangle.

 

HRESULT SaveObject(void);

 

The component program calls SaveObject() when it wants to be saved to its storage. The container calls IPersistStorage::Save.

 

The IAdviseSink Interface

 

Containers implement this interface. Embedded object handlers call its functions in response to component notifications. The IAdviseSink interface enables containers and other objects to receive notifications of data changes, view changes, and compound-document changes occurring in objects of interest. Container applications, for example, require such notifications to keep cached presentations of their linked and embedded objects up-to-date. Calls to IAdviseSink methods are asynchronous, so the call is sent and then the next instruction is executed without waiting for the call's return.

For an advisory connection to exist, the object that is to receive notifications must implement IAdviseSink, and the objects in which it is interested must implement IOleObject::Advise and IDataObject::DAdvise. In-process objects and handlers may also implement IViewObject::SetAdvise. Objects implementing IOleObject must support all reasonable advisory methods. To simplify advisory notifications, OLE supplies implementations of the IDataAdviseHolder and IOleAdviseHolder, which keep track of advisory connections and send notifications to the proper sinks through pointers to their IAdviseSink interfaces. IViewObject (and its advisory methods) is implemented in the default handler.

As shown in the following table, an object that has implemented an advise sink registers its interest in receiving certain types of notifications by calling the appropriate method:

 

Call This Method

To Register for These Notifications

IOleObject::Advise

When a document is saved, closed, or renamed.

IDataObject::DAdvise

When a document's data changes.

IViewObject::SetAdvise

When a document's presentation changes.

 

Table 9.

 

When an event occurs that applies to a registered notification type, the object application calls the appropriate IAdviseSink method. For example, when an embedded object closes, it calls the IAdviseSink::OnClose method to notify its container. These notifications are asynchronous, occurring after the events that trigger them.

 

When to Implement

 

Objects, such as container applications and compound documents, implement IAdviseSink to receive notification of changes in data, presentation, name, or state of their linked and embedded objects. Implementers register for one or more types of notification, depending on their needs.

Notifications of changes to an embedded object originate in the server and flow to the container by way of the object handler. If the object is a linked object, the OLE link object intercepts the notifications from the object handler and notifies the container directly. All containers, the object handler, and the OLE link object register for OLE notifications. The typical container also registers for view notifications. Data notifications are usually sent to the OLE link object and object handler.

 

When to Use

 

Servers call the methods of IAdviseSink to notify objects with which they have an advisory connection of changes in an object's data, view, name, or state. OLE does not permit synchronous calls in the implementation of asynchronous methods, so you cannot make synchronous calls within any of the IAdviseSink interface's methods. For example, an implementation of IAdviseSink::OnDataChange cannot contain a call to IDataObject::GetData. Methods available in IAdviseSink are listed below.

 

IUnknown Methods

Description

QueryInterface()

Returns pointers to supported interfaces.

AddRef()

Increments reference count.

Release()

Decrements reference count.

 

Table 10.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

IAdviseSink Methods

Description

OnDataChange()

Advises that data has changed.

OnViewChange()

Advises that view of object has changed.

OnRename()

Advises that name of object has changed.

OnSave()

Advises that object has been saved to disk.

OnClose()

Advises that object has been closed.

 

Table 11.

 

Important methods used in this module are:

 

void OnClose(void);

 

Component programs call this function when they are being terminated.

 

void OnViewChange(DWORD dwAspect, …);

 

The handler calls OnViewChange() when the metafile has changed. Because the component program must have been running for this notification to have been sent, the handler can call the component's IDataObject::GetData function to get the latest metafile for its cache. The container can then draw this metafile by calling IViewObject2::Draw.

 

OLE Helper Functions

 

A number of global OLE functions encapsulate a sequence of OLE interface calls. Following are some that we'll use in the EX32B example:

 

HRESULT OleCreate(REFCLSID rclsid, REFIID riid, …, IOleClientSite* pClientSite, IStorage* pStg, void** ppvObj);

 

The OleCreate() function first executes the COM creation sequence using the specified class ID. This loads the component program. Then the function calls QueryInterface() for an IPersistStorage pointer, which it uses to call InitNew(), passing the pStg parameter. It also calls QueryInterface() to get an IOleObject pointer, which it uses to call SetClientSite() using the pClientSite parameter. Finally it calls QueryInterface() for the interface specified by riid, which is usually IID_IOleObject.

 

HRESULT OleCreateFromData(IDataObject* pSrcDataObj, REFIID riid, …, IOleClientSite* pClientSite, IStorage* pStg, void** ppvObj);

 

The OleCreateFromData() function creates an embedded object from a data object. In the EX32B example, the incoming data object has the CF_EMBEDDEDOBJECT format with an IStorage pointer. The function then loads the component program based on the class ID in the storage, and then it calls IPersistStorage::Load to make the component load the object's native data. Along the way, it calls IOleObject::SetClientSite.

 

HRESULT OleDraw(IUnknown* pUnk, DWORD dwAspect, HDC hdcDraw, LPCRECT lprcBounds);

 

This function calls QueryInterface() on pUnk to get an IViewObject pointer, and then it calls IViewObject::Draw, passing the lprcBounds parameter.

 

HRESULT OleLoad(IStorage* pStg, REFIID riid, IOleClientSite* pClientSite, void** ppvObj);

 

The OleLoad() function first executes the COM creation sequence by using the class ID in the specified storage. Then it calls IOleObject::SetClientSite and IPersistStorage::Load. Finally, it calls QueryInterface() for the interface specified by riid, which is usually IID_IOleObject.

 

HRESULT OleSave(IPersistStorage* pPS, IStorage* pStg, …);

 

This function calls IPersistStorage::GetClassID to get the object's class ID, and then it writes that class ID in the storage specified by pStg. Finally it calls IPersistStorage::Save.

 

An OLE Embedding Container Application

 

Now that we've got a working mini-server that supports embedding (EX32A), we'll write a container program to run it. We're not going to use the MFC container support, however, because you need to see what's happening at the OLE interface level. We will use the MFC document-view architecture and the MFC interface maps, and we'll also use the MFC data object classes.

 

MFC Support for OLE Containers

 

If you did use AppWizard to build an MFC OLE container application, you'd get a class derived from COleDocument and a class derived from COleClientItem. These MFC base classes implement a number of important OLE container interfaces for embedding and in-place activation. The idea is that you have one COleClientItem object for each embedded object in a single container document. Each COleClientItem object defines a site, which is where the component object lives in the window.

The COleDocument class maintains a list of client items, but it's up to you to specify how to select an item and how to synchronize the metafile's position with the in-place frame position. AppWizard generates a basic container application with no support for linking, clipboard processing, or drag and drop. If you want those features, you might be better off looking at the MFC DRAWCLI and OCLIENT samples. We will use one MFC OLE class in the container, COleInsertDialog. This class wraps the OleUIInsertObject function, which invokes the standard Insert Object dialog box. This Insert Object dialog enables the user to select from a list of registered component programs.

 

Some Container Limitations

 

Because our container application is designed for learning, we'll make some simplifications to reduce the bulk of the code. First of all, this container won't support in-place activation; it allows the user to edit embedded objects only in a separate window. Also, the container supports only one embedded item per document, and that means there's no linking support. The container uses a structured storage file to hold the document's embedded item, but it handles the storage directly, bypassing the framework's serialization system. Clipboard support is provided; drag-and-drop support is not. Outside these limitations, however, it's a pretty good container!

 

Container Features

 

So, what does the container actually do? Here's a list of features:

  1. As an MFC MDI application, it handles multiple documents.

  2. Displays the component's metafile in a sizeable, moveable tracker rectangle in the view window.

  3. Maintains a temporary storage for each embedded object.

  4. Implements the Insert Object menu option, which allows the user to select a registered component. The selected component program starts in its own window.

  5. Allows embedded objects to be copied (and cut) to the clipboard and pasted. These objects can be transferred to and from other containers such as Microsoft Word and Microsoft Excel.

  6. Allows an embedded object to be deleted.

  7. Tracks the component program's loaded-running transitions and hatches the tracker rectangle when the component is running or active.

  8. Redraws the embedded object's metafile on receipt of component change notifications.

  9. Saves the object in its temporary storage when the component updates the object or exits.

  10. Copies the embedded object's temporary storage to and from named storage files in response to Copy To and Paste From commands on the Edit menu.

 

 

 

 

 

 

 

 

 

 

 

 

 

Further reading and digging:

  1. DCOM at MSDN.

  2. COM+ at MSDN.

  3. COM at MSDN.

  4. Win32 process, thread and synchronization story can be found starting from Module R.

  5. MSDN MFC 7.0 class library online documentation.

  6. MSDN MFC 9.0 class library online documentation - latest version.

  7. MSDN Library

  8. Windows data type.

  9. Win32 programming Tutorial.

  10. The best of C/C++, MFC, Windows and other related books.

  11. Unicode and Multibyte character set: Story and program examples.

 

 


 

| Tenouk C & C++ | MFC Home | OLE & Containers 1 | OLE & Containers 3 | Download | Site Index |