IMoniker: Composite Group

A number of IMoniker members apply specifically to composites. This means that simple monikers can leave many of these functions unimplemented. We have seen the operation of Enum and IsSystemMoniker; all we need to add to our discussion of Enum is that fForward specifies the direction of enumeration, left to right (when fForward is TRUE) or right to left (when fForward is FALSE), depending on the needs of the caller.

The core member in the Composite group is ComposeWith. Even simple monikers (including the anti-moniker) implement it. The function is given a "suffix"—that is, a moniker to its right (pmkRight)—and is asked to create a composite containing itself and that suffix. ComposeWith basically gives a moniker the chance to control the type of composite used, generic or nongeneric. The moniker being asked to compose can call pmkRight->GetClassID (or use some other means, such as querying for a particular custom interface) to determine whether that type would be understood by a nongeneric composite. If not, the fOnlyIfNotGeneric flag controls what should happen. If this flag is TRUE, ComposeWith should return a NULL in *ppmkComposite and the result MK_E_NEEDGENERIC. This basically says that the moniker and pmkRight support only generic composition. If fOnlyIfNotGeneric is FALSE, the same two monikers could be combined generically with CreateGenericComposite.

It is interesting that if pmkRight happens to be an anti-moniker or something that completely negates the moniker being called, ComposeWith returns a NULL in *ppmkComposite and the NOERROR result, regardless of the value of fOnlyIfNotGeneric.

The functionality of the IMoniker::Inverse function is closely related to this idea of negation. An inverse of a moniker (which can be written as ~moniker or moniker-1) is defined as a moniker such that a NULL composition results when that inverse is passed to the moniker's ComposeWith (moniker·inverse). Some monikers, such as the anti-moniker, have no inverse at all, and most inverse monikers have no inverses themselves. The reason is that an inverse of one specific moniker is usually the inverse of a certain superset of monikers with the same structure as the original one. For example, a file moniker with the path DATA\OLE\SLIDES has an inverse of ..\..\... Composing the two results in nothing at all. Well, ..\..\.. is also the inverse of \TEXT\2ND_ED\FIGURES as well as FOO\BAR\BAZ, literally anything in the form <x>\<y>\<z>.

The inverse of a composite moniker is basically a composite that contains the inverses of each contained moniker in reverse order. That is, the inverse of File!Item1!Item2 would be Item2-1!Item1-1!File-1, which may be nothing more than Anti!Anti!File-1. The anti-moniker is again the generic inverse of any simple moniker that has no internal structure.

CommonPrefixWith and RelativePathTo

The member functions CommonPrefixWith and RelativePathTo are related to inverses but exist primarily for dealing with composites that contain a file moniker. When a client maintains a link to some object in another file, it is useful for that client to retain two monikers for that link: one absolute, the other relative. This is exactly how a linked compound document object stores its link information, as we'll see in Chapter 20. CommonPrefixWith and RelativePathTo provide the means to create a relative moniker.

CommonPrefixWith asks a moniker to determine how much it has in common with the moniker passed to it in pmkOther, returning the result (which can be a composite) in *ppmkPrefix. For example, a composite containing C:\DATA\OLE\SPEC.DOC!Page2!Table1 and a file moniker with C:\DATA\NOTES\1995.DOC would have the common prefix C:\DATA in a single file moniker. C:\DATA\OLE\SPEC.DOC!Page2!Table1 and C:\DATA\OLE\SPEC.DOC!Page2!Table7 would have the common prefix C:\DATA\OLE\SPEC.DOC!Page2 in a File!Item composite. A few return codes for this function indicate special relationships—for example, MK_S_NOPREFIX (no common prefix exists), MK_S_HIM (pmkOther is already the prefix of this moniker), and MK_S_US (this and pmkOther are equal).

The RelativePathTo function works along similar lines: the moniker is asked to return a relative moniker so that moniker·relative results in the moniker passed in pmkOther. So if we ask C:\DATA\OLE\SPEC.DOC!Page2!Table1 to return the relative that would result in C:\DATA\OLE\SPEC.DOC, it could return an Anti!Anti composite. Or if asked for the relative to produce C:\DATA, it would return Anti!Anti!File, in which the file moniker contains "..". If RelativePathTo returns a meaningful relative, it should return the NOERROR result; if pmkOther is the only relative form of the moniker called, it returns MK_S_HIM to say so.

In both of these functions, pmkOther is often the moniker for the container document (or file, spreadsheet, and so on) that manages the link. If I have a container document C:\DATA\OLE\SPEC.DOC in which is linked C:\DATA\PICTURES\PUFFINS.BMP, we can create a relative moniker that contains ..\PICTURES\PUFFINS.BMP. To do this, we call the absolute path moniker's CommonPrefixWith with the container's moniker to get C:\DATA and then call RelativePathTo on the container moniker passing the common prefix to get "..". We then strip the common prefix from the linked object's moniker and compose the remainder with the relative path moniker. The result is a moniker describing the linked object in relation to the document.

If all of this sounds like a lot of work for a moniker to implement, you're right. For that reason, OLE provides default implementations through the API functions MonikerCommonPrefixWith and MonikerRelativePathTo. A moniker should first check whether it has intimate knowledge of pmkOther—that is, whether the moniker knows that it can handle pmkOther in a special way. If not, the moniker should use these two API functions for its implementation in order to handle generic composites correctly. MonikerRelativePathTo can also be called from a client as a simple wrapper for IMoniker::RelativePathTo. This is not true of MonikerCommonPrefixWith; clients should call IMoniker::CommonPrefixWith directly.

Reduce

The final member of the composite group, Reduce, asks a moniker to rewrite itself into an equivalent but more efficient form, which has several uses:

A somewhat contrived example of reduction is shown in Figure 9-6. It may not look like a reduction, but it illustrates the evaluation of various sorts of alias monikers into their appropriate values in the host environment. The particular classes of monikers shown here are illustrative only—OLE does not implement such monikers. In any case, you can see that many monikers in this example reduce to something totally different, whereas others do not.

Figure 9-6.

An example of moniker reduction.

In this example, the top moniker is essentially decompressed into its exact form. Other monikers might reduce to something that is actually smaller, performing garbage collection. For example, a composite that contains three file monikers (some relative, some absolute) can reduce a mess like \\BUNNYKINS\CDRIVE\DATA\OLE2\OLD\..\..\OLE\..\..\TEXT\2ND_ED\CH09.DOC to \\BUNNYKINS\CDRIVE\DATA\OLE\CH09.DOC.

Many monikers, especially simple ones, reduce to themselves, in which case Reduce returns MK_S_REDUCED_TO_SELF. Otherwise, dwReduceHowFar, taken from the MKRREDUCE enumeration, indicates to what extent the reduction should proceed:


typedef enum tagMKREDUCE
{
MKRREDUCE_ONE = 3<<16,
MKRREDUCE_TOUSER = 2<<16,
MKRREDUCE_THROUGHUSER = 1<<16,
MKRREDUCE_ALL = 0
} MKRREDUCE;

MKRREDUCE_ONE instructs the moniker to reduce "one step," which usually means the caller has intimate knowledge of what that step will accomplish. MKRREDUCE_TOUSER asks the moniker to reduce to something whose display name would be meaningful to an end user—that is, a display name that uses filenames, bookmarks, cell ranges, database queries, and other recognizable item names that appear in the link source's user interface. Internal names (like aliases) should not be part of such a display. Closely related is MKRREDUCE_THROUGHUSER, which tells the moniker to reduce to the point at which any further reduction would render an unrecognizable display name from the user's point of view. This is often the same thing that MKRREDUCE_TOUSER does. Finally, MKRREDUCE_ALL tells the moniker to reduce as far as possible so that any later requests to reduce return MK_S_REDUCED_TO_SELF. Monikers that have no user-readable display names treat all other flags except MKRREDUCE_ONE exactly as they would treat MKRREDUCE_ALL.