Binding a Composite Moniker

The individual binding operations of most simple monikers make sense only in the context of a composite. The binding processes for those simple monikers that can stand alone are also quite simple, as we've already seen with the file and pointer monikers. You might expect that because a composite usually contains more than one element within it, binding is just a matter of following the map, right? You only have to follow each step in order to get to the right place…or do you? There are, in fact, many reasons why following the entire sequence isn't necessary.

Composite monikers actually bind in a right to left order (instead of left to right), which has the practical effect of doing only as much binding as necessary. When following a map, you do not always start at the beginning—if your present location is already partway down the map. A composite moniker achieves this by reading the map in reverse order until it comes to a deterministic point. From that point, it can bind forward again.

A composite actually delegates most of its binding to the monikers within it by using the following algorithm, executed in its IMoniker::BindToObject (and IMoniker::BindToStorage):

(BindToObject only) Check whether the object named by this composite is currently registered in the running object table. If so, the object's IUnknown pointer is available from there, so the composite retrieves it and calls QueryInterface to obtain the pointer to return to the client. See "Binding Optimizations I: The Running Object Table" later in this chapter for more details about the running object table.

Separate the composite into two pieces: a rightmost, or last, moniker (pmkRight) and a composite containing the left, or all but last, moniker (pmkLeft). The composite as a whole can be viewed as AllButLast!Last or Left!Right for this purpose.

Call pmkRight->BindToObject, passing pmkLeft as an argument. Whatever this rightmost moniker returns from BindToObject is what the composite returns from its own BindToObject.

The result of separating a composite into Left!Right is depicted in Figure 9-3 on the next page. In general, the Right moniker requires some services of the Left moniker in order to bind itself, which is why the composite passes Left as an argument to Right's BindToObject. Thus, Right usually calls pmkLeft->BindToObject again, and if Left is itself a composite, we'll go right back through this algorithm again.

Figure 9-3.

Binding a composite moniker splits the moniker into left and right pieces and then calls BindToObject on the right moniker passing the left moniker. (The services of the left moniker may be required by the right.)

This algorithm effectively walks backward through the composite, asking each piece to bind in turn. Three conditions will stop this right to left progression:

A Composite Binding Example

To illustrate this process, let's walk through the binding of a File!Item1!Item2 composite, in which the names Item1 and Item2 help us to distinguish which moniker is which. Let's say we have a client with a pointer to this moniker in the variable pmkComp. That client wants to obtain an IProvideClassInfo pointer for the object named by this moniker, so it calls pmkComp->BindToObject(, IID_IDescription, (void **)&pIDesc) to get the pointer. No problem.

Now we enter what we'll call the outer composite's BindToObject. According to the algorithm, BindToObject checks the running object table first. Let's say the object is not running (or else it would take all the fun away). The composite divides itself into left and right pieces, where the left piece is a File!Item1 composite and the right piece is simply Item2. The outer composite then calls Item2's BindToObject, passing File!Item1 as pmkLeft.

As described before, an item moniker must have a context in which to make sense of its persistent name. It depends on the pmkLeft it receives to provide that context in the form of an IOleItemContainer interface. So Item2 now calls pmkLeft->BindToObject(, IID_IOleItemContainer, (void **)&pCont2), which puts us into the File!Item1 composite. Once again, this second composite checks whether the object is running, and failing that, it splits itself into File and Item1 monikers. It then calls Item1's BindToObject, passing File as pmkLeft. Item1, being an item moniker, also needs a context in which to make sense of its persistent name, so it calls pmkLeft->BindToObject(, IID_IOleItemContainer, (void **)&pCont1), where pmkLeft is the File moniker.

At this point, we are four levels deep into various BindToObject calls. The outer composite called Item2, which then called Item1, which has now called the File moniker. Fortunately, this File moniker doesn't depend on anything to its left, so it takes its filename, calls GetClassFile, CoCreateInstance, and IPersistFile::Load(pszFile). After loading is complete, the File moniker calls IPersistFile::QueryInterface(riid1, ppv1), where riid1 and ppv1 are the same arguments that the File moniker received in its BindToObject call. These arguments will be IID_IOleItemContainer and pCont1 as passed from Item1.

The object that implements IPersistFile must also support IOleItemContainer to support binding this File!Item1!Item2 moniker. This should not be a surprise to that object because its server (the source) is probably what gave away the composite moniker in the first place. Either that, or the client synthesized the moniker knowing that the object supported such binding. This is the big rule about implementing any link-source server: if you give away the moniker, you have to provide the support for binding it.

Anyway, the File moniker succeeds in obtaining the IOleItemContainer pointer, which it now returns to Item1. This item moniker calls IOleItemContainer::GetObject(pszItem1, riid2, ppv2), where riid2 and ppv2 are the arguments that Item1's BindToObject received from Item2. The GetObject member now must find the object named by pszItem1, which is the name stored in Item1. In something like a File!Item!Item moniker this name might be for a specific sheet in a workbook or for a specific page in a document—that is, an item in the overall file that is itself a container for still other items. This GetObject member will find the correct object for pszItem1 and return whatever interface pointer it was asked for. In this example, we have one implementation of IOleItemContainer returning a pointer to the same interface but on a different object. In other words, Item2 requires an IOleItemContainer pointer from Item1, which also required the same pointer from File. But the containers named by File and Item1 are completely separate. It is only because we have two item monikers in a row that IOleItemContainer comes up twice.

So the file object's IOleItemContainer resolves pszItem1 into another IOleItemContainer interface on the Item1 object, and this pointer is returned to Item2. This item then calls IOleItemContainer::GetObject(pszItem2, riidOrg, ppvOrg), where pszItem2 is Item2's persistent name and riidOrg and ppvOrg are the arguments originally passed from the client, in this case IID_IDescription and &pIDesc. The implementation of GetObject (which is part of the object named by Item1) finds the object identified with pszItem2 and returns the correct interface pointer to it. This interface pointer is implemented on the object named by Item2, in the context of File!Item1, which is exactly the object named by the File!Item1!Item2 composite. Item2 then returns this pointer from its own BindToObject, which the outer composite returns all the way back to the client.

This process is illustrated in Figure 9-4, assuming that a single server implements the File, Item1, and Item2 objects within the same process, which is usually the case when such a source hands out a File!Item!Item moniker.

Through the binding process, you can see how a composite delays as much work as possible, especially when we add the optimizations discussed in the next section. If, for example, the server in this example had already opened the file described by the File moniker and registered it in the running object table, the File moniker would not have had to call CoCreate-Instance at all. It would only need to extract the file object's IUnknown from the running object table and call its QueryInterface. Furthermore, if the server had had the file open and had been already viewing whatever object is described by Item1 (page, sheet, and so on), the File!Item1 moniker would have been registered as running; thus, binding File!Item1 would have gone no further than to extract a pointer and a query for IOleItemContainer.

Figure 9-4.

Binding a File!Item1!Item2 moniker.

The IOleItemContainer, IOleContainer, and IParseDisplayName Interfaces

Before we look at the running object table and the bind context, let's first look at IOleItemContainer, which plays such a prominent role in binding an item moniker. This interface derives from an interface named IOleContainer, which is itself derived from one named IParseDisplayName:


interface IParseDisplayName : IUnknown
{
HRESULT ParseDisplayName(IBindContext *pbc, LPOLESTR pszDisplayName
, ULONG *pchEaten, IMoniker **ppmkOut);
};

interface IOleContainer : IParseDisplayName
{
HRESULT EnumObjects(DWORD grfFlags, LPENUMUNKNOWN *ppEnum);
HRESULT LockContainer(BOOL fLock);
};

interface IOleItemContainer : IOleContainer
{
HRESULT GetObject(LPOLESTR pszItem, DWORD dwSpeedNeeded
, IBindCtx *pbc, REFIID riid, void **ppv);
HRESULT GetObjectStorage(LPOLESTR pszItem, IBindCtx *pbc
, REFIID riid, void **ppvStorage);
HRESULT IsRunning(LPOLESTR pszItem);
};

We've already seen the use of GetObject and its pszItem, riid, and ppv arguments. Its pbc argument is a pointer to a bind context, described later in "Binding Optimizations II: The Bind Context," and dwSpeedNeeded indicates just how long the moniker wants to wait for the container to parse the item name into an object, taken from the BINDSPEED enumeration:


typedef enum tagBINDSPEED
{
BINDSPEED_INDEFINITE = 1, //Can wait forever.
BINDSPEED_MODERATE = 2, //Will wait ~2.5 seconds.
BINDSPEED_IMMEDIATE = 3 //Object must be running.
} BINDSPEED;

If the container cannot retrieve the object according to this limit, it should return MK_E_EXCEEDEDDEADLINE.

The other two members of IOleItemContainer are quite straightforward. GetObjectStorage is to IMoniker::BindToStorage as GetObject is to IMoniker::BindToObject. IsRunning asks the container whether the object identified with the item name is already up and running so that requesting a pointer to it is immediate.

IOleContainer has only two members specific to itself. The first, EnumObjects, is asked to create an enumerator with IEnumUnknown, which iterates over a specific set of object types in the container according to the combination of OLECONTF flags passed in grfFlags:


typedef enum tagOLECONTF
{
OLECONTF_EMBEDDINGS = 1, //OLE Document embeddings only
OLECONTF_LINKS = 2, //OLE Document linkings only
OLECONTF_OTHERS = 4, //Anything outside OLE Documents
OLECONTF_ONLYUSER = 8, //Objects visible to end user
OLECONTF_ONLYIFRUNNING = 16 //Running objects
} OLECONTF;

(OLECONTF_OTHERS is basically the set of all objects that are not otherwise included with OLECONTF_EMBEDDINGS and OLECONTF_LINKS.)

The other function in IOleContainer is LockContainer, which gives any object inside this container a way to tell the container to stay in memory (when fLock is TRUE) or to release itself if necessary (when fLock is FALSE). A container will typically call CoLockObjectExternal to create or remove a strong lock on itself in response to this call.

Finally we come to IParseDisplayName and its single member ParseDisplayName. This function is asked to turn a display name string into a moniker. More precisely, the container is asked to parse as much of the display name as it can, from left to right, returning an appropriate moniker for what it parsed. The out-parameter pchEaten specifies how many characters were parsed out of the display name; if more remain, the caller will need to parse the rest of that name. We'll see later how this comes into play when we look at display names in the context of the IMoniker interface in "IMoniker: Display Name Group."