In a nutshell, OLE's naming technology can be described as a client having an IMoniker pointer to some moniker where calling IMoniker::BindToObject does whatever is necessary to access the named object and returns one of that object's interface pointers to the client, as illustrated in Figure 9-1.
Figure 9-1.
A client asks a moniker to bind to the object named by the moniker.
From the client's perspective, this process is as simple as calling an interface member function for a local or remote object is. To make the process even simpler, OLE offers a wrapper function named BindMoniker with the following signature:
HRESULT BindMoniker(IMoniker *pmk, DWORD grfOpt, REFIID riid, void **ppv);
where pmk is the moniker (name) to bind, grfOpt is reserved and must be 0, and riid and ppv specify the interface pointer that the client wants for the object named by this moniker. Thus the entire idea of naming and binding is wrapped up inside this single API function, which represents the extent to which a simple client has to concern itself with monikers.
Well, if it were that simple we wouldn't need a whole chapter on the subject, would we? As you probably expect, there is actually much more to this picture than meets the eye because a binding operation can take a significant amount of time to execute and can involve any number of intermediate objects and their servers—binding can load any number of DLLs, launch any number of EXEs, load any number of files or objects, and so on.
Therefore, we must first know how a client obtains a moniker in the first place, which happens through any of the four techniques that we saw in Chapter 2: call a specific API function, call a generic API function, call an interface member function in some other object, or receive the pointer through your own object's interface. Which technique applies depends on the type of moniker or the origin of that moniker. In some cases, a client can create a new moniker itself. In other cases, some other source or server of links will create the name and make it available to the client through a data transfer mechanism such as the clipboard or drag and drop.
The most important type of name is called a composite moniker, which is little more than a collection of other monikers (including other composites). A composite moniker allows creation (or composition) of arbitrarily complex names, and its IPersistStream implementation simply asks each contained moniker to save or load in turn. An example of a very complex name is one for a specific paragraph in a specific section of a specific document in a specific database on a specific network server in a specific network domain on a specific network system.
Creating the name is only half of naming and binding: we also need to see what it means to bind a moniker. Our discussion will be specifically oriented to binding a composite because any other moniker is really just a degenerate form of a composite. Binding a composite means binding all of its contained monikers in a particular sequence to avoid as much extra work as possible. Once we have examined this binding, we'll have a good overall picture of what naming and binding are all about.
OLE also goes beyond the basic architecture and provides a few mechanisms to optimize the binding process. The first optimization is to track objects that are already in a running state through what is called the running object table. If an object is already running, binding to it is nothing more than extracting its pointer from the table and querying for the desired interface pointer, and this saves a tremendous amount of time.
In addition, maintaining some sort of state through the entire composite binding process is advantageous, and for that reason OLE provides an object called the bind context. This object holds flags controlling the binding and acts as a repository of the intermediate objects that have already been bound in the entire process. The intermediate objects and servers can also save properties or parameters inside the bind context that remain active as long as the binding is in progress.
But before we get into the details, let me introduce an analogy that will help us understand how monikers work—a treasure map. When you read the words "treasure map," what comes to mind? Probably some old piece of crusty parchment with a series of instructions or directions scrawled on it, telling you how to navigate through a hazardous terrain to get to the big X, where the treasure lies. The data stored in a moniker is like a treasure map, but the moniker also contains the intelligence to follow the map and return with the treasure. The client of such a treasure map needs only to ask the map, "Go get the treasure and bring it back." A moniker will do this without any reservation, returning an interface pointer to the object, which is indeed a treasure to a client.
As a specific example of this analogy, let's imagine that we spent a day in my kitchen baking some cookies and that we then placed them in a cookie jar. We set the jar on the third shelf of the pantry in my house (not sure how I'd talk you into letting me keep them) in the city of Bothell, in the state of Washington, in the United States of America (which is on the continent of North America, on the planet Earth, and so on). As cookie junkies, we want to make treasure maps that tell us exactly where these cookies are, so no matter where we go, we know how to get back to them.