Although monikers were originally developed to enable OLE to deal with objects that were linked to a compound document in a standard way, monikers are now important for different reasons altogether. To understand how you can use monikers as part of a component-based design, it is instructive to examine some of the newer system monikers implemented as part of COM+. The class moniker is an excellent example of modern implementations of the IMoniker interface, so we'll use it as a starting point for understanding how monikers can be integrated with current software projects. The genius of the class moniker is its utter simplicity: it wraps the CLSID of a coclass. Thus, the class moniker enables a client application, using the standard IMoniker interface, to reference a coclass identified by a CLSID. Actually, the class moniker always returns a pointer to the class object of the named coclass, so the caller can instantiate the object itself. In fact, the class moniker is so simple that in most cases its implementation of the IMoniker::BindToObject function simply calls CoGetClassObject, as shown here:
HRESULT CClassMoniker::BindToObject(IBindCtx *pbc, IMoniker *pmkToLeft, REFIID riidResult, void **ppvResult) { BIND_OPTS2 bopts; bopts.cbStruct = sizeof(bopts); pbc->GetBindOptions(&bopts); if(pmkToLeft == NULL) return CoGetClassObject(m_clsid, bopts.dwClassContext, 0, riidResult, ppvResult); // Code to deal with moniker to the left omitted... // Since a composite moniker is a collection of other // monikers stored in left-to-right sequence, there // might be a moniker to the left of this class moniker. } |
Using the class moniker via the MkParseDisplayName function, you can name any object based on its CLSID and then activate it by calling the IMoniker::BindToObject method. You can use this method instead of the more standard activation sequence of CoGetClassObject followed by IClassFactory::CreateInstance, as executed by CoCreateInstance. Binding to a coclass named by a class moniker, however, does not in itself instantiate the object. Only a pointer to the class object is returned by the binding operation, which means that in order to instantiate the object you still have to call IClassFactory::CreateInstance or a method of some custom activation interface.
The class moniker works for the same reasons that the imaginary Hello moniker described earlier does; no special support for class monikers is built into the MkParseDisplayName function. When a string that has the form clsid:10000013-0000-0000-0000-000000000001 is passed to the function MkParseDisplayName, the clsid program identifier is searched for in the registry. Of course, we all know about the HKEY_CLASSES_ROOT\CLSID section of the registry—after all, it's one of the most important areas of the registry in which coclasses are registered. To the MkParseDisplayName function, however, HKEY_CLASSES_ROOT\CLSID is just another ProgID in the registry. Once it locates this ProgID, the function opens the HKEY_CLASSES_ROOT\CLSID\clsid subkey, which contains the actual CLSID of the class moniker.
When you use a tool such as the registry editor (regedit.exe), this clsid subkey is visible as the final entry in the HKEY_CLASSES_ROOT\CLSID section of the registry, after all of the true class identifiers. The CLSID of the class moniker can be searched for in HKEY_CLASSES_ROOT\CLSID\{0000031A-0000-0000-C000-000000000046}, where the InprocServer32 key has the value ole32.dll, which tells you that the class moniker is implemented as part of ole32.dll. This neat setup is what makes the class moniker work.
Once a moniker has been returned to the client, it can immediately bind to the underlying object using the IMoniker::BindToObject method. The following code uses MkParseDisplayName to obtain the moniker and then, by binding the moniker to the named object, obtains a pointer to the Prime class object. Using the custom activation interface implemented by the class object, you can create the Prime object and then call its GetNextPrime method. Then all the interface pointers must be released.
// We always need a bind context. IBindCtx* pBindCtx; CreateBindCtx(0, &pBindCtx); // Convert the string to a moniker. ULONG eaten; IMoniker* pMoniker; OLECHAR string[] = L"clsid:10000013-0000-0000-0000-000000000001"; MkParseDisplayName(pBindCtx, string, &eaten, &pMoniker); // Bind the moniker to the named object. IPrimeFactory* pPrimeFactory; pMoniker->BindToObject(pBindCtx, NULL, IID_IPrimeFactory, (void**)&pPrimeFactory); // Use the custom class object to create a Prime object. IPrime* pPrime; pPrimeFactory->CreatePrime(7, &pPrime); // Now we have a Prime object. int next_prime; pPrime->GetNextPrime(&next_prime); cout << next_prime << endl; // Displays 11 // Release all. pPrimeFactory->Release(); pPrime->Release(); pBindCtx->Release(); pMoniker->Release(); |
By this time, you are almost certainly wondering what the advantages are of using MkParseDisplayName and IMoniker::BindToObject compared to simply calling CoGetClassObject. The fact that you can deal with any coclass using the standard IMoniker interface is a powerful concept. In this instance, you can combine what you know about the class moniker with Visual Basic's GetObject function to make it possible for an application written in Visual Basic to access the IPrimeFactory custom activation interface implemented by the class object of the Prime coclass.7 The code below shows how it's done:
Dim myPrimeFactory As IPrimeFactory Dim myPrime As IPrime ' Call MkParseDisplayName and IMoniker::BindToObject. Set myPrimeFactory = _ GetObject("clsid:10000013-0000-0000-0000-000000000001") ' Call IPrimeFactory::CreatePrime. Set myPrime = myPrimeFactory.CreatePrime(7) ' Call IPrime::GetNextPrime. Print myPrime.GetNextPrime ' Displays 11 |
The class moniker is powerful in its own right. It even offers Visual Basic programmers access to custom activation interfaces implemented by class objects. But it is missing one important piece of functionality: the ability to name coclasses on remote machines. As you've seen, the class moniker takes a string in the form clsid:10000013-0000-0000-0000-0000000000001 and converts it to a moniker after performing a lookup in the local registry. The class moniker would be much more powerful if it could be supplied with a string in the form host:myserver!clsid:10000013-0000-0000-0000-0000000000001. (An exclamation point is typically used to delineate the sections of a composite moniker.) This form would enable you to create a moniker that names a coclass on another computer. Imagine the power of a Visual Basic program that could pass a magic string to the GetObject function that would provide access to any coclass on any machine!
Although COM+ does not currently provide this functionality in the system-supplied class moniker, Microsoft did consider that other developers might want to extend the class moniker by adding the ability to name classes on other machines. To this end, the class moniker is aware that it might have another moniker to its left. If it is told that such a moniker exists, the class moniker attempts to determine whether the moniker to its left implements the IClassActivator interface. The IClassActivator interface is a hook that lets you modify the default behavior of the class moniker. The IClassActivator interface is shown here in IDL notation:
interface IClassActivator : IUnknown { HRESULT GetClassObject( [in] REFCLSID rclsid, [in] DWORD dwClassContext, [in] LCID locale, [in] REFIID riid, [out, iid_is(riid)] void **ppv); } |
Because this functionality is not currently available in the system, let's implement a custom moniker that provides the functionality needed to redirect the class moniker to another machine.8 We'll build this functionality into a custom moniker called the marvelous moniker. To create a custom moniker, you generally begin with a standard in-process component. The object will be a moniker, so it must implement the IMoniker interface, which includes the methods of IPersistStream and IPersist as well as IUnknown. If the moniker is to be accessible via the MkParseDisplayName function, it must also have a registered ProgID. Since the desired display name of the moniker is host:myserver!clsid:????????-????-????-????-????????????, the registered ProgID must be host.9 The registration procedure for this moniker, shown in the following code, creates the correct registry entries:
HRESULT __stdcall DllRegisterServer() { // The ProgID host must be registered // in order for the moniker to work. return RegisterServer("moniker.dll", CLSID_MarvelousMoniker, "Marvelous Moniker", "Host", "Host", NULL); } |
With the registration in place, all calls to the MkParseDisplayName function beginning with the string Host are directed to the CLSID of the moniker (CLSID_MarvelousMoniker). The class object of the moniker is then obtained using CoGetClassObject, and QueryInterface is called to determine whether the moniker's class object implements the IParseDisplayName interface. Thus, the class object of a moniker typically implements only the IParseDisplayName activation interface; there is no need to implement IClassFactory. The IParseDisplayName interface is defined to enable MkParseDisplayName to load a moniker and request that it parse its own display name, since obviously MkParseDisplayName is a general function and doesn't have any information about the string format of a specific custom moniker. The IParseDisplayName interface is shown here in IDL notation:
interface IParseDisplayName : IUnknown { HRESULT ParseDisplayName ( [in, unique] IBindCtx *pbc, [in] LPOLESTR pszDisplayName, [out] ULONG *pchEaten, [out] IMoniker **ppmkOut ); } |
The IParseDisplayName interface has only one method: ParseDisplayName. The first moniker to be instantiated by MkParseDisplayName is handed the entire string provided by the caller. Its job is to digest as much of the string as possible and return a moniker that names the object specified in the string. The following code shows the marvelous moniker's implementation of the IParseDisplayName::ParseDisplayName method. This method simply parses the display name into its two components: the host name and the CLSID. These values are stored in the moniker's member variables for later use during the binding operation.
HRESULT CClassObject::ParseDisplayName(IBindCtx *pbc, LPOLESTR pszDisplayName, ULONG *pchEaten, IMoniker **ppmkOut) { // Instantiate the moniker. CMarvyMoniker* pCMarvyMoniker = new CMarvyMoniker(); // Parse and check the display name. // It must have the following format: // host:hostname!clsid:????????-????-????-????-???????????? if(_wcsicmp(wcstok(pszDisplayName, L":"), L"host") == 0) { pCMarvyMoniker->m_CoServerInfo.pwszName = wcscpy(pCMarvyMoniker->m_hostname, wcstok(NULL, L"!")); if(_wcsicmp(wcstok(NULL, L":"), L"clsid") == 0) { wchar_t clsid_with_braces[39] = L"{"; wcscat(wcscat(clsid_with_braces, wcstok(NULL, L"!")), L"}"); CLSIDFromString(clsid_with_braces, &pCMarvyMoniker->m_clsid); } } // Get IMoniker* to return to caller. pCMarvyMoniker->QueryInterface(IID_IMoniker, (void**)ppmkOut); pCMarvyMoniker->Release(); // Indicate that we have digested the entire display name. *pchEaten = (ULONG)wcslen(pszDisplayName); return S_OK; } |
Once the MkParseDisplayName function returns, the client holds a valid moniker that names a unique coclass on a particular machine. To use the object named by the moniker, the client typically binds to the object by calling the IMoniker::BindToObject method, as shown here:
IPrimeFactory* pPrimeFactory; pMoniker->BindToObject(pBindCtx, NULL, IID_IPrimeFactory, (void**)&pPrimeFactory); |
Now comes the hard part: the moniker must actually instantiate the coclass on the machine identified in the moniker's display name. Since the marvelous moniker is really an extension of the system-supplied class moniker, as part of its implementation of the IMoniker::BindToObject method it creates a standard class moniker for the specified CLSID using the CreateClassMoniker function. Next, the moniker binds to the object by calling the class moniker's implementation of the IMoniker::BindToObject method. The steps are shown in the following code:
// An AddRef a day keeps the doctor away... AddRef(); // Create a normal class moniker for this CLSID. IMoniker* pClassMoniker; HRESULT hr = CreateClassMoniker(m_clsid, &pClassMoniker); // Bind to the COM class named by the class moniker; // tell the class moniker that we are to its left. hr = pClassMoniker->BindToObject(pbc, (IMoniker*)this, riidResult, ppvResult); pClassMoniker->Release(); |
Typically, the class moniker's implementation of the IMoniker::BindToObject method simply calls CoGetClassObject. However, if the class moniker is aware that there is a moniker to its left, it takes special action. Notice that in the BindToObject call in the preceding code, the second parameter of the BindToObject call is a this pointer that informs the class moniker that the marvelous moniker is to its left. The following pseudo-code shows the steps executed by the class moniker in its BindToObject method; the section dealing with the moniker to its left is shown in boldface:
HRESULT CClassMoniker::BindToObject(IBindCtx *pbc, IMoniker *pmkToLeft, REFIID riidResult, void **ppvResult) { BIND_OPTS2 bopts; bopts.cbStruct = sizeof(bopts); pbc->GetBindOptions(&bopts); if(pmkToLeft == NULL) return CoGetClassObject(m_clsid, bopts.dwClassContext, 0, riidResult, ppvResult); // Code to deal with moniker to the left... // Make a recursive call to the BindToObject method // of the moniker on the left to obtain a // pointer to the IClassActivator interface. IClassActivator* pActivate; pmkToLeft->BindToObject(pbc, IID_IClassActivator, (void**)&pActivate); // Call IClassActivator::GetClassObject. HRESULT hr = pActivate->GetClassObject(m_clsid, bopts.dwClassContext, bopts.locale, riidResult, ppvResult); pActivate->Release(); return hr; } |
Note that if the class moniker discovers another moniker to its left, it makes a recursive call to that moniker's BindToObject method. This recursive call has an effect similar to that of the QueryInterface method because the class moniker is attempting to obtain a pointer to our moniker's implementation of the IClassActivator interface. To trap this recursive call from the class moniker and to avoid an endless loop of recursion, the marvelous moniker's implementation of the BindToObject method first checks whether someone is attempting to obtain a pointer to the IClassActivator interface. If so, it simply casts the this reference to an IClassActivator interface pointer and returns S_OK. The marvelous moniker's implementation of the IMoniker::BindToObject method is shown below; the section relevant to the IClassActivator interface is in boldface:
HRESULT CMarvyMoniker::BindToObject(IBindCtx *pbc, IMoniker *pmkToLeft, REFIID riidResult, void **ppvResult) { // This catches the recursive call by the class moniker. if(riidResult == IID_IClassActivator) { *ppvResult = (IClassActivator*)this; return S_OK; } // An AddRef a day keeps the doctor away... AddRef(); // Create a normal class moniker for this CLSID. IMoniker* pClassMoniker; HRESULT hr = CreateClassMoniker(m_clsid, &pClassMoniker); if(FAILED(hr)) return hr; // Bind to the COM class named by the class moniker; // tell the moniker that the new moniker is to its left. hr = pClassMoniker->BindToObject(pbc, (IMoniker*)this, riidResult, ppvResult); pClassMoniker->Release(); return hr; } |
Because the marvelous moniker itself implements the IClassActivator interface in addition to the IMoniker interface, a simple cast in the BindToObject method is sufficient. As you saw in the pseudo-code for the class moniker's implementation of the IMoniker::BindToObject method, the class moniker calls the sole method of the IClassActivator interface: GetClassObject. The GetClassObject method retrieves the class object of the coclass named by the moniker. This hook method lets you override the default local machine behavior of the class moniker and redirect it to bind with a class object on a specific host. The marvelous moniker's implementation of the IClassActivator::GetClassObject method simply calls CoGetClassObject. However, it uses the COSERVERINFO structure to specify the host name from which the class object is to be retrieved.10 The implementation of the GetClassObject method is shown here:
HRESULT CMarvyMoniker::GetClassObject(REFCLSID pClassID, DWORD dwClsContext, LCID locale, REFIID riid, void** ppv) { // Call CoGetClassObject using the COSERVERINFO structure // that contains the host name from the moniker's // display name. return CoGetClassObject(pClassID, CLSCTX_SERVER, &m_CoServerInfo, riid, ppv); } |
The CoGetClassObject function in the preceding code obtains and returns the class object from the specified host. The pointer to the remote class object is returned to the class moniker, back to the marvelous moniker, and finally to the client. The client can then use the class object without regard to the fact that the object was instantiated on a remote machine. Once installed and registered, the marvelous moniker can be used from any language. From C++, you simply call MkParseDisplayName or the CoGetObject helper function; in Visual Basic, you call the GetObject function. Here is a snippet of Visual Basic code that uses the marvelous moniker to access the Prime coclass on any machine:
Dim myPrimeFactory As IPrimeFactory Dim myPrime As IPrime ' Call MkParseDisplayName. Set myPrimeFactory = GetObject( _ "host:HostNameHere!clsid:10000013-0000-0000-0000-000000000001") ' Call IPrimeFactory::CreatePrime. Set myPrime = myPrimeFactory.CreatePrime(7) Dim Count As Integer For Count = 0 To 10 ' Call IPrime::GetNextPrime. Print myPrime.GetNextPrime Next |
The marvelous moniker also exports a moniker creation function named CreateMarvelousMoniker. This function enables a marvelous moniker to be created directly rather than from a display name. The declaration for the CreateMarvelousMoniker function is shown here:
HRESULT CreateMarvelousMoniker(REFCLSID clsid, COSERVERINFO* pCoServerInfo, IMoniker** ppMoniker) |