Monikers and Composition

Monikers are often composed from other monikers to allow object hierarchies to be navigated based on a textual description of a path. To support this type of navigation easily, COM provides a standard moniker implementation that, when composed to the right of another moniker, asks the object to bind a reference to another object in the hierarchy. This moniker is called the Item Moniker, and it uses the object’s IOleItemContainer interface to resolve an object name to an interface pointer.

The following display name demonstrates the Item Moniker used in tandem with the Class Moniker:

clsid:571F1680-CC83-11d0-8C48-0080C73925BA:!Ursus

Note the use of the “!” character to delimit the Class Moniker’s display name from the item name “Ursus”. When parsed, MkParseDisplayName will first use the prefix “clsid” as a ProgID to contact the implementation of the Class Moniker. MkParseDisplayName will then ask the Class Moniker implementation to parse as much of the string as it recognizes. This means that after the Class Moniker has parsed its GUID from the string, the following fragment will still need to be parsed:

!Ursus

Because this name is meaningful only in the scope of the object named by the moniker to its left, MkParseDisplayName will actually bind the leftmost moniker (the Class Moniker) and ask the object it names (the Gorilla class object) to parse the remainder of the string. To support parsing display names, COM defines the standard interface IParseDisplayName:

[ object,uuid(0000011a-0000-0000-C000-000000000046) ]
interface IParseDisplayName : IUnknown {
// convert display name to a moniker
  HRESULT ParseDisplayName(
        [in, unique] IBindCtx *pbc,
        [in] LPOLESTR pszDisplayName,
        [out] ULONG *pchEaten,
        [out] IMoniker **ppmkOut
  );
}

In the case of the display name used in this example, the Gorilla class object would need to implement IParseDisplayName and convert the string “!Ursus” to a moniker that MkParseDisplayName will compose to the right of the Class Moniker. Because the standard Item Moniker is desired, the following implementation would suffice:

STDMETHODIMP GorillaClass::ParseDisplayName(IBindCtx *pbc,
              LPOLESTR pszDisplayName, ULONG *pchEaten,
              IMoniker **ppmkOut) {
// create an item moniker using explicit API function
  HRESULT hr = CreateItemMoniker(OLESTR("!"), 
                               pszDisplayName + 1,
                               ppmkOut);
// indicate how many characters were parsed
  if (SUCCEEDED(hr)) 
    *pchEaten = wcslen(pszDisplayName);
  else
    *pchEaten = 0;
  return hr;
}

Note that this example does not attempt to validate the name it is parsing. It simply shears off the leading “!” and creates a new Item Moniker from the remaining display name.

Once the two monikers have been parsed, MkParseDisplayName will couple the two monikers together using a generic composite moniker. Generic composite monikers simply hold two monikers together. The generic composite moniker’s implementation of BindToObject simply binds the moniker on the right first, passing the pointer to the moniker on the left as the pmkToLeft parameter. The following pseudocode illustrates this:

// pseudo-code from OLE32.DLL
STDMETHODIMP GenericComposite::BindToObject (IBindCtx *pbc,
              IMoniker *pmkToLeft, 
              REFIID riid, void **ppv) {
  return m_pmkRight->BindToObject(pbc, m_pmkLeft, riid, ppv);
}

This implementation illustrates that the moniker on the right is meaningful only in the scope of the moniker to its left. In the case of the Class!Item Moniker used in this example, the Item Moniker will receive the Class Moniker as its pmkToLeft parameter at bind time.

As stated earlier, the Item Moniker uses the IOleItemContainer interface to bind an interface pointer. The following is the pseudocode for the Item Moniker’s BindToObject implementation:

// pseudo-code from OLE32.DLL
STDMETHODIMP ItemMoniker::BindToObject(
              IMoniker *pmkToLeft, IBindCtx *pbc,
              REFIID riid, void **ppv) {
// assume failure
  *ppv = 0;
  if (pmkToLeft == 0) // requires a scope
    return E_INVALIDARG;  
// first bind moniker to left
  IOleItemContainer *poic = 0;
  HRESULT hr = pmkToLeft->BindToObject(0 ,pbc, 
                    IID_IOleItemContainer, (void**)&poic);
  if (SUCCEEDED(hr)) {
// cache the bound object in binding context
   pbc->RegisterObjectBound(poic);
// get bind speed from Bind Context
   DWORD dwBindSpeed = this->MyGetSpeedFromCtx(pbc);
// ask object for named sub-object
    hr = poic->GetObject(m_pszItem, dwBindSpeed, pbc, 
                       riid, ppv);
    poic->Release();
  }
}

This implementation implies that the following code:

HRESULT GetUrsus(IApe *&rpApe) {
  const OLECHAR pwsz[] = 
OLESTR("clsid:571F1680-CC83-11d0-8C48-0080C73925BA:!Ursus");
  return CoGetObject(pwsz, 0, IID_IApe, (void**)&rpApe);
}

is equivalent to

HRESULT GetUrsus(IApe *&rpApe) {
  IOleItemContainer *poic = 0;
  HRESULT hr = CoGetClassObject(CLSID_Gorilla, CLSCTX_ALL,
                  0, IID_IOleItemContainer, (void**)&poic);
  if (SUCCEEDED(hr)) {
    hr = poic->GetObject(OLESTR("Ursus"), BINDSPEED_INFINITE, 
                       0, IID_IApe, (void**)&rpApe);
    poic->Release();
  }
  return hr;
}

Note that the level of indirection afforded by using CoGetObject allows the client to change the binding policy simply by reading a different display name from a configuration file or registry key.

© 1998 by Addison Wesley Longman, Inc. All rights reserved.