After following our analysis to this point, you probably have two questions on your mind:
Monikers are the answer to the first question, and consequently they provide the answer to the second question. As mentioned earlier, a moniker is an object that names another object. Names are used throughout Windows to identify everything from files to event objects. The difficulty is that every type of object has its own naming rules. Monikers allow all objects to deal with naming through a single, standard interface: IMoniker. COM+ provides several system implementations of the IMoniker interface, described in the following table. Notice that monikers are not instantiated by calling CoCreateInstance but instead offer their own custom creation function because most monikers require extra information about the object they name.
Moniker Type | Creation Function | Purpose |
---|---|---|
File moniker | CreateFileMoniker | A file moniker acts as a wrapper for the pathname of a file. |
Item moniker | CreateItemMoniker | An item moniker identifies an object contained in another object. |
Pointer moniker | CreatePointerMoniker | A pointer moniker identifies an object that can exist only in the active or running state. |
Anti-moniker | CreateAntiMoniker | An anti-moniker is the inverse of another moniker; when the two are combined, they obliterate each other. |
Composite moniker | CreateGenericComposite | A composite moniker is composed of other monikers. |
Class moniker | CreateClassMoniker | A class moniker acts as a wrapper for the CLSID of a COM class. |
URL moniker | CreateURLMoniker | A URL moniker represents and manages a Uniform Resource Locator (URL). |
OBJREF moniker* | CreateObjrefMoniker | An OBJREF moniker encapsulates a marshaled IUnknown interface pointer to an object. |
* For information about the OBJREF moniker, see Chapter 15.
In many cases, one of the system monikers implemented by COM+—or some combination of these monikers—will suffice for the purpose of naming objects. At other times, only a custom moniker will do. A custom moniker is an object you create that implements the IMoniker interface. Contrary to popular belief, implementing the IMoniker interface is not that difficult.
The myth that the IMoniker interface is difficult to implement probably arose because the interface has 15 methods. To ensure that all moniker objects support persistence, IMoniker is derived from the IPersistStream interface (4 methods), which is derived from the IPersist interface (1 method), which is in turn derived from the IUnknown interface (3 methods). That makes a grand total of 23 methods required to implement a custom moniker. This interface hierarchy is shown in Figure 11-1.
Figure 11-1. The IMoniker interface hierarchy.
In practice, not all monikers need to implement all of the methods. Many of the IMoniker methods were designed primarily for file monikers and thus are not applicable to certain custom monikers. In addition, not all monikers need to support persistence, which means that the methods of the IPersist and IPersistStream interfaces might simply return E_NOTIMPL. The system-supplied pointer moniker operates in this fashion.
A warning flag should be raised in your mind whenever several methods of an interface implementation return E_NOTIMPL. Either the implementation is incomplete or certain methods of that interface are not applicable. If, as is the case with pointer monikers, certain methods of an interface simply are not relevant to a specific implementation, this might indicate that the interface was designed improperly. Monikers do not take advantage of the richness offered by the IUnknown::QueryInterface method to determine the capabilities offered by a particular moniker coclass. Every implementation of the IMoniker interface is forced to provide an implementation of the IPersistStream and IPersist interfaces, even if those methods do nothing more than return E_NOTIMPL. This design flaw is more annoyance than limitation, but it does illustrate the importance of careful interface design.
The following table describes the methods of the IMoniker interface.
IMoniker Method | Description |
---|---|
BindToObject | Binds to the object named by the moniker |
BindToStorage | Binds to the object's storage |
Reduce | Reduces the moniker to its simplest form |
ComposeWith | Combines the moniker with another moniker to create a composite moniker (a collection of monikers stored in left-to-right sequence) |
Enum | Enumerates component monikers |
IsEqual | Compares the moniker with another moniker |
Hash | Returns a hash value |
IsRunning | Checks whether the object is running |
GetTimeOfLastChange | Returns time the object was last changed |
Inverse | Returns the inverse of the moniker |
CommonPrefixWith | Finds the prefix that the moniker has in common with another moniker |
RelativePathTo | Constructs a relative moniker between this moniker and another |
GetDisplayName | Returns the display name |
ParseDisplayName | Converts a display name to a moniker |
IsSystemMoniker | Checks whether the moniker is one of the system-supplied types |
Roughly half of IMoniker's methods take a pointer to a bind context as an argument. A bind context is an object that implements the IBindCtx interface and contains information about a moniker binding operation. The IBindCtx interface is shown below in IDL notation:
interface IBindCtx : IUnknown { // Registers an object with the bind context HRESULT RegisterObjectBound([in, unique] IUnknown* punk); // Revokes an object's registration HRESULT RevokeObjectBound([in, unique] IUnknown* punk); // Releases all objects registered via RegisterObjectBound HRESULT ReleaseBoundObjects(void); // Sets the binding options HRESULT SetBindOptions([in] BIND_OPTS* pbindopts); // Returns the binding options stored in this bind context HRESULT GetBindOptions([in, out] BIND_OPTS* pbindopts); // Returns a pointer to the IRunningObjectTable interface HRESULT GetRunningObjectTable( [out] IRunningObjectTable** pprot); // Associates an object with a string key HRESULT RegisterObjectParam([in] LPOLESTR pszKey, [in, unique] IUnknown* punk); // Returns the object associated with a given string key HRESULT GetObjectParam([in] LPOLESTR pszKey, [out] IUnknown** ppunk); // Enumerates all the string keys in the table HRESULT EnumObjectParam([out] IEnumString** ppenum); // Revokes association between an object and a string key HRESULT RevokeObjectParam([in] LPOLESTR pszKey); } |
A binding operation connects a moniker with the object that it names. In some situations, a bind context enables certain optimizations to occur during the binding of composite monikers. There is no need to implement the IBindCtx interface because the system provides a fully functional implementation of this interface. To create a bind context for use with a moniker, you simply call the CreateBindCtx function, as shown here:
IBindCtx* pBindCtx; CreateBindCtx(0, &pBindCtx); |
The heart of the IMoniker interface is the BindToObject method. This method enables a moniker to bind to the object it names. A client application might call this method as shown in the code below. Notice the use of the pBindCtx parameter passed to the IMoniker::BindToObject method.
IMyInterface* pMyInterface; pMoniker->BindToObject(pBindCtx, NULL, IID_IMyInterface, (void**)&pMyInterface); |
Once a moniker is bound to the underlying object that it names, a pointer to the requested interface of that object is returned to the client application. The client can proceed to use the object in the typical fashion by calling any methods of the requested interface. When the client has finished working with the object, the object must be released, along with the bind context and the moniker itself.
To reify monikers without an explicit binding context, use the helper function BindMoniker. The BindMoniker function takes an IMoniker interface pointer and binds that moniker to the object it names. The bind context required to perform the binding operation is automatically acquired by the BindMoniker function. In the following code fragment, pMoniker is a pointer to a moniker that is being bound to the underlying object it names. Notice the explicit use of a bind context:
IBindCtx* pClassBindCtx; CreateBindCtx(0, &pClassBindCtx); pMoniker->BindToObject(pClassBindCtx, NULL, IID_IUnknown, (void**)&pUnknown); pClassBindCtx->Release(); |
The functionally equivalent code shown here uses the BindMoniker helper function, resulting in code that is significantly easier to read:
BindMoniker(pMoniker, 0, IID_IUnknown, (void**)&pUnknown); |
The primary reason you might bother to explicitly create a bind context and then call the IMoniker::BindToObject method instead of using the BindMoniker function is if you need to set custom options in the bind context. Bind context options are set using the IBindCtx::SetBindOptions method, which takes a pointer to a BIND_OPTS or BIND_OPTS2 structure. For example, using the BIND_OPTS2 structure, you can set the pServerInfo member to point to a COSERVERINFO structure containing the name of the machine on which the binding operation should occur.4 The BIND_OPTS2 structure is shown below:
typedef struct tagBIND_OPTS2 { DWORD cbStruct; // sizeof(BIND_OPTS2) DWORD grfFlags; // BIND_FLAGS DWORD grfMode; // STGM flags DWORD dwTickCountDeadline; // How long should // binding take DWORD dwTrackFlags; // SLR flags DWORD dwClassContext; // CLSCTX flags LCID locale; // Passed to // IClassActivator COSERVERINFO* pServerInfo; // Server name and // security } BIND_OPTS2; |
A coclass is identified by a CLSID; a running COM+ object is identified by a marshaled interface pointer. Instead of using a CLSID or marshaled interface pointer, monikers employ a somewhat user-friendly string called a display name to identify COM+ objects. Monikers require that string names be used to represent objects in much the same way that a file system identifies files—at least to the user—by string name. For example, a file moniker's display name takes the form of a path, such as C:\My Documents\Story.doc, whereas a class moniker supports strings in the form clsid:10000013-0000-0000-0000-0000000000001.
The IMoniker::GetDisplayName method returns the display name of any moniker; and the IMoniker::ParseDisplayName method converts a display name into a moniker object. Of course, before you can call either the GetDisplayName or ParseDisplayName methods, you must already have a moniker object of the desired type. This is where the MkParseDisplayName function comes in, as defined here:
HRESULT MkParseDisplayName(IBindCtx* pbc, LPCWSTR szDisplayName, ULONG* pchEaten, IMoniker** ppmk); |
Technically speaking, MkParseDisplayName converts a string to a moniker that identifies the object named by that string. This process is similar to calling the IMoniker::ParseDisplayName method, except that you don't have to have a moniker to begin with—a string is sufficient. You can use a string because the MkParseDisplayName function is the entry point into the COM+ namespace; a custom moniker can hook into this namespace and provide any user-defined naming functionality you require.
MkParseDisplayName accepts two primary string formats. The first format is a pathname, such as C:\My Documents\Letter.doc. MkParseDisplayName has hard-coded support for file monikers and thus knows that any pathname should be converted to a file moniker. The second string format is the more general and thus more important of the two formats. In this format, MkParseDisplayName accepts any string in the form ProgID:ObjectName, where ProgID is a registered program identifier. This architecture allows anyone to write a custom moniker that hooks into the COM+ namespace simply by creating a program identifier (ProgID) entry in the registry.
The following steps are executed when MkParseDisplayName encounters a string that has the ProgID:ObjectName format:
For example, if the string "Hello:Maya" is passed to MkParseDisplayName, the HKEY_CLASSES_ROOT section of the registry is searched for the ProgID Hello. If Hello is found, the CLSID subkey below the ProgID is used to locate and load the moniker. The moniker's IParseDisplayName::ParseDisplayName method is then called to create a moniker object that names the Maya object. Figure 11-2 shows the registry entries involved in this hypothetical example; the numbered labels indicate the order in which the information is obtained from the registry.
Figure 11-2. The steps to locate a moniker's coclass from a ProgID.
Once you obtain an IMoniker interface pointer using MkParseDisplayName, the next step is typically to bind to the object named by the moniker. You can do this by creating an explicit bind context and then calling the IMoniker::BindToObject method; or you can use the BindMoniker helper function discussed previously. Yet another helper function, CoGetObject,5 simplifies the process of converting a display name into a moniker and then binding to the named object. Like the BindMoniker function, CoGetObject obviates the need for an explicit binding context. Unlike BindMoniker, however, CoGetObject also lets you set binding options using the BIND_OPTS(2) structures. Below is pseudo-code for the internal implementation of the CoGetObject function:
HRESULT __stdcall CoGetObject(LPCWSTR pszDisplayName, BIND_OPTS* pBindOptions, REFIID riid, void** ppv) { HRESULT hr = 0; // Create the bind context. IBindCtx* pBindCtx = 0; hr = CreateBindCtx(0, &pBindCtx); // Call MkParseDisplayName with the user's string. ULONG chEaten; IMoniker* pMoniker = 0; hr = MkParseDisplayName(pBindCtx, pszDisplayName, &chEaten, &pMoniker); // Set the bind options requested by the caller. hr = pBindCtx->SetBindOptions(pBindOptions); // Call IMoniker::BindToObject to get the user's object. hr = pMoniker->BindToObject(pBindCtx, NULL, riid, ppv); // Release stuff. pMoniker->Release(); pBindCtx->Release(); return hr; } |
One of the neat things about the CoGetObject wrapper for MkParseDisplayName is that Visual Basic programmers already know and love this function. Well, actually, most Visual Basic programmers know the MkParseDisplayName function by a different moniker—they call it GetObject. Like the CoGetObject helper function designed for C++ developers, Visual Basic's GetObject function takes a string and internally calls MkParseDisplayName to obtain a moniker. Then it calls the IMoniker::BindToObject method of the moniker to obtain a pointer to the object requested by the Visual Basic programmer in the string passed to GetObject. C++ pseudo-code for Visual Basic's GetObject function is shown below:6
IUnknown* GetObject(LPCOLESTR szDisplayName) { IUnknown* pUnknown = 0; // Convert a display name to a moniker, // and then bind to the object. CoGetObject(szDisplayName, 0, IID_IUnknown, (void**)&pUnknown); // Return a pointer to the object's IUnknown. return pUnknown; } |
Java has no native analogue to the CoGetObject function. Instead, the J/Direct feature of the Microsoft Java Virtual Machine (VM) enables Java programs to call native Win32 API functions; you can use it to directly invoke the standard CoGetObject function. To avoid forcing Java developers to provide J/Direct declarations for the functions of the Win32 API, Microsoft provides these in the com.ms.win32 package. The COM+ library of API primitives is defined in the com.ms.win.Ole32 class, of which CoGetObject is a method.