How do you write a treasure map? In other words, how does one create a new moniker? This can happen either in the server of the object being named or directly inside a client. In the former case, the server creates the moniker and makes it available to the client through a data transfer mechanism such as the clipboard. The client then performs a Paste Link to obtain that moniker for its own use—that is, to get an IMoniker pointer. In this way, a client obtains a moniker without knowing anything at all about the treasure map itself. In some cases, however, the client knows the location of the treasure and can simply create a moniker itself. In this sense, the client is using a moniker as a convenient encapsulation, perhaps so the rest of the client's code can use monikers for all names instead of having code to use monikers in some cases and specific code for other cases. Dealing with all names as monikers can greatly simplify design and improve performance and efficiency.
Both clients and servers use the same means to create a moniker. The means depend on the moniker class in question, which is either a standard OLE-provided moniker class or a custom moniker class. Both categories include simple monikers and composite monikers; the simple monikers are generally useful only inside a composite.
Regardless of the means of creation, all moniker types must have a run-time identity. So, every moniker class must be assigned its own CLSID, standard and custom monikers alike. At run time, this CLSID is retrieved through IMoniker::GetClassID (which comes from IPersist). In addition, IMoniker::IsSystemMoniker returns a flag from the MKSYS enumeration, identifying whether the moniker is a standard or a custom type:
enum tagMKSYS
{
MKSYS_NONE = 0,
MKSYS_GENERICCOMPOSITE = 1,
MKSYS_FILEMONIKER = 2,
MKSYS_ANTIMONIKER = 3,
MKSYS_ITEMMONIKER = 4,
MKSYS_POINTERMONIKER = 5
} MKSYS;
All custom monikers must return MKSYS_NONE from this function, whereas OLE's standard implementations are allowed to return the other values. (Microsoft reserves the right to add new values in the future—don't depend on this list to be permanent.) With that in mind, let's see a few details of these standard monikers.
What? More classes? I thought I finished all my classes in college! Ah, but they never told you about monikers. Actually, OLE implements four simple moniker classes: file moniker, item moniker, pointer moniker, and anti-moniker.4
Of these, the file and pointer monikers are useful outside a composite, and only file and item monikers have a persistent state. Anti-monikers and pointer monikers have no persistent states, and the anti-moniker is not even bindable. We'll see what the anti-moniker does later in this chapter.
OLE exports a different creation function for each simple moniker, as described in Table 9-1. Each function returns the new moniker in an out-parameter ppmk (of type IMoniker **); other arguments provide the moni-kers with their state data, which for the file and item monikers becomes the information they can read and write persistently. Each moniker also has its own CLSID of the form 0000030x-0000-0000-C000-000000000046, where x is 3 for file monikers, 4 for item, 5 for anti, and 6 for pointer.
Function | Description |
CreateFileMoniker(pszPath, ppmk) | Creates a file moniker, given any portion of a pathname in pszFile. The portion can be as short as a drive letter or as long as a complete path. The file moniker converts pszPath to a standard UNC path. Anything the operating system understands as a path is suitable for a file moniker. |
CreateItemMoniker(pszDelim, pszItem, ppmk) | Creates an item moniker for which pszItem identifies the item's name and pszDelim identifies a delimiter string (usually a single character such as !), which does not occur elsewhere in pszItem. This delimiter is used to prefix the item's display name, allowing it to be combined with other items in the same string and parsed out of that string again. |
CreatePointerMoniker(pIUnknown, ppmk) | Creates a pointer moniker to encapsulate the pointer passed in pIUnknown. This makes any object look like a moniker. |
CreateAntiMoniker(ppmk) | Creates an anti-moniker, which needs no extra information. |
Table 9-1.
OLE API functions that create simple monikers.
Because a file moniker manages a pathname, it is useful by itself. Binding a single file moniker finds a CLSID associated with the file, instantiates an object of that class, and has it load the file through IPersistFile::Load. A pointer moniker is also useful by itself but only as an encapsulation of the object it names. Binding a pointer moniker simply calls the object's QueryInterface, allowing you to write code that can treat all passive or running objects exclusively through IMoniker.
Item monikers, on the other hand, must be part of a composite. By design, an item moniker depends on whatever moniker precedes it in a composite (the moniker to its left) in order to define the item's context—that is, to uniquely identify what the item is part of. For example, say our treasure map indicates, "Cookie jar is on the third shelf in the pantry in the kitchen of the house at 723 East Satori Street." If we made this sequence from individual item monikers, the "third shelf" makes no sense outside of "pantry," which must be found in a kitchen, which is not unique unless you define the house. Even then, the address isn't unique. As an item moniker, that address also requires a city, state, and country, such as a file moniker containing USA\Washington\Bothell.5 A composite with all these elements would uniquely identify which cookie jar we're talking about.
Binding an item moniker depends on some implementation of IOleItemContainer, specifically its members GetObject and GetObjectStorage, to interpret the name in the item and return the object it refers to. When asked to bind, an item moniker asks the moniker to its left to bind and return an IOleItemContainer pointer through which the item can then resolve its data into a pointer to return to the client.
The standard file and item monikers are sufficient for the vast majority of naming situations. An implementation of IOleItemContainer::GetObject can encapsulate any sort of process or intelligence it wants for any item. For example, you could write a server to process a composite file and item moniker that actually names a database query. The file names the database, and the item names the query.
In rare situations, however, you may want to use a more efficient moniker to encapsulate more intelligence within the moniker instead of within an IOleItemContainer implementation. This is the case where you might use a custom moniker, which is nothing more than an in-process object that implements IMoniker (and possibly IROTData; see "The IROTData Interface and Custom Monikers" on page 464). In other words, a custom moniker has nothing special over the standard run-of-the-mill custom component that we saw in Chapter 5. You implement the object, stuff it in an in-process server, and register that server. Anything that wants an instance of your moniker requires only the CLSID (or ProgID) to pass to CoCreateInstance.
When you are tempted to create a custom moniker, ask yourself whether you really, really have to resort to that. You may find that some composite moniker is good enough. The few cases where you really do need a custom moniker are those in which the persistent state of the moniker is binary (file and item monikers work with text strings) or when you require special behavior when creating a composite moniker with a custom moniker.
As you know, a client's call to CoCreateInstance creates an uninitialized object, so how is that client to get initialization arguments to the custom moniker? You can do this in a number of ways. First, you could ship your own API function to create the custom moniker and forgo the component business altogether. A second option is to create a custom interface and implement it on the class factory with a "create instance with this data" function. You can also add another initialization interface on the moniker itself, for example IDataObject, IDispatch, or a custom interface. The other option is to take advantage of the fact that IPersistStream is the base interface for IMoniker and that IPersistStream::Load is an initialization function. If the client knows the correct stream format for your custom moniker, it can create a stream, store arguments in it (which will be exactly the same as your moniker's persistent state), and then call your IMoniker::Load. This solution (and those dealing with other standard interfaces) requires you to publish only a data structure as opposed to a custom interface (all of which can be done in type information, of course). Which method you choose depends on your potential customers. At the time of writing, Microsoft has not set any sort of standard because there are so many other satisfactory means.
Once created, the persistent state of a moniker usually remains constant, or the moniker has no persistent state at all. In either case, you can call the state immutable, making a moniker a prime candidate for custom marshaling, through which both the client and the server of the named object use identical copies of the moniker in their own processes. Simply said, a moniker can write its own persistent data into its marshaling packet (a stream) in IMarshal::MarshalInterface. The proxy is just another copy of the same moniker that initializes itself with the same data. No interprocess communication is necessary because both monikers are equivalent.
Once again, a composite moniker is a collection of any other monikers, including other composites, that knows how to manage the relationships between its constituent monikers. A composite is the mechanism through which you create an arbitrary name of any size and complexity. A composite moniker allows each simple moniker within it to concentrate on one sort of naming and binding mechanism, completely eliminating redundant implementation in different moniker types. For example, creating a separate "part-of-a-file" moniker isn't necessary because you can create a composite with the existing file and item monikers to achieve the same goal. In mathematical terms, composites form the set of all possible permutations and combinations of the set of simple monikers.
OLE provides a standard implementation called the generic composite moniker, which is created through the OLE API function CreateGenericComposite:
HRESULT CreateGenericComposite(IMoniker *pmkFirst, IMoniker *pmkRest
, IMoniker **ppmkComposite);
This function basically glues together pmkFirst and pmkRest, returning the composite in ppmkComposite. (CreateGenericComposite does not call Release in pmkFirst or pmkRest. The caller is still responsible for them.)
A composite moniker stores its constituent monikers left to right, as shown in Figure 9-2. Just as a treasure map is made up of many individual instructions, a composite is a collection of individual monikers that each represent an instruction. Some of those instructions can be simple; others may be more complex. In any case, the composition is considered associative in that composing moniker A with B and composing that result with C—(A·B)·C—produces the same moniker as composing B with C and composing the result with A—that is, A·(B·C).
Figure 9-2.
A composite moniker stores other monikers inside it in a left to right sequence.
The persistent state of a composite moniker is nothing more than a stream containing the persistent states of every constituent moniker. The generic composite implements its IPersistStream::Save by calling OleSaveToStream for each moniker within it; IPersistStream::Load simply calls OleLoadFromStream on each moniker, which instantiates and initializes all those monikers without the composite having to do any other work.
OLE's generic composite knows nothing about the specific monikers inside it. You may, however, find occasion to create a composite moniker of your own that understands more about its constituents and their relationship, making optimizations based on the types of monikers therein. This is the reason why each moniker class must have a unique CLSID obtainable at run time through IMoniker::GetClassID—there is no other robust way for a nongeneric composite to know its own contents.
A number of member functions in the IMoniker interface are relevant to composites. ComposeWith tells any moniker to create a composite using itself and another moniker to attach to its right. Some monikers, such as the file moniker, do not use the generic composite to implement their ComposeWith member. It would be silly to attach two filenames together. Because the file moniker implementation of ComposeWith understands what filenames are and how they act, it will merge the two filenames into a single file moniker, a degenerate nongeneric composite. The composites in this case are called nongeneric.
The Enum member of IMoniker returns an enumerator through which the caller can iterate through the individual monikers in the composite, using the IEnumMoniker interface. (The enumerated elements are IMoniker pointers.) Inverse asks any given moniker for another moniker so that a composite containing the moniker and its inverse effectively annihilates the moniker. For example, the inverse of the path \DATA\OLE is \..\..—the composite of the two yields nothing. The concept of an inverse is the purpose of the anti-moniker, which really acts as a handy generic inverse for simple monikers—such as item and pointer monikers—that have no special internal structure (which a file moniker does). Composing an anti-moniker with a simple moniker annihilates them both. When composed with another composite, the anti-moniker effectively removes the last moniker in that composite.
Finally there is IMoniker::Reduce, which asks the moniker to create an equivalent moniker with a more efficient form. This can effectively mean decompress or compress depending on the type of composite. For example, you might have some sort of moniker that is nothing more than an alias for two others. Reducing this alias is the same as resolving it into the real value—that is, into the other monikers that make a precise name.
The discussions that follow describe a composite according to the types of monikers contained within it. We've seen a few examples of this notation already. The first convention for naming a composite is <Type>!<Type>!<Type>!…, where <Type> might be File, Item, Anti, Pointer, Comp[osite], or whatever custom name you want, with an exclamation point—pronounced "bang"—serving as the delimiter between types ("!" is typically used in generating display names from a composite).
So a File!Item moniker—pronounced "file-bang-item"—is a composite with one file and one item moniker. File!Item!Item has one file and two item monikers, whereas File!Item!Item!Pointer!Anti is effectively the same as File!Item!Item, but it describes exactly what is contained in the composite. It is also useful to number multiple items of the same type to distinguish those items, as in File!Item1!Item2. Finally, because a composite can contain other composites, Comp!Item is also a suitable notation.
A second notation uses a character such as · to denote "composed with," as in File·Item. This notation is used most often in the context of creating a composite, and the use of the ! delimiter refers to the structure of a composite after it's been created.
4 Two other types DDECompositeMoniker and PackagerMoniker are used internally in OLE for the same reasons mentioned previously regarding centralization of naming intelligence. I mention them because you will see their CLSIDs in the registry. |
5 I do actually live in this city, but this is not my address. (I'm not crazy enough to publish it.) But don't worry, I'll watch the cookies. |