Interface and Implementation Again

The previous examples of client-side activation made explicit calls to COM activation API functions. Often, multiple steps may be needed to properly bind to the desired object (e.g., create one type of object, then ask it for a reference to another object based on some query information). To decouple clients from the actual algorithms used for finding or creating objects, COM provides a standard mechanism for resolving arbitrary object names onto the objects to which they refer. This mechanism is based on using locator objects that encapsulate their binding algorithm behind a standard, uniform interface. These locator objects are formally called monikers and are simply COM objects that export the IMoniker interface. The IMoniker interface is one of the more complex COM interfaces; however, it exposes one method that is extremely important for this discussion, BindToObject:

interface IMoniker : IPersistStream {
  HRESULT BindToObject([in] IBindCtx *pbc, 
   [in, unique] IMoniker *pmkToLeft,
   [in] REFIID riid,
   [out, iid_is(riid)] void **ppv);
// remaining methods deleted for clarity
}

Recall that interface definitions are abstract and sufficiently vague to allow many interpretations of each method’s precise semantics. The abstract semantics of BindToObject are “run your algorithm for finding or creating an object and return a typed interface pointer to the object once it is created or found.” When a client calls BindToObject on a moniker, it has no idea of exactly how the moniker will resolve its internal state into a pointer to an object. Given three different monikers, three completely different algorithms may be used. This polymorphic behavior is what makes the moniker idiom so powerful.

Clients get monikers in one of several ways. A client may be given a moniker by some external agent, such as a result from a method call on some object already in use. Clients may call an explicit API function that creates a particular type of moniker. Clients may simply have a text string that is the “stringified” state of the moniker. This last situation is the most interesting, as it allows applications to load and store text-based “object names” using external configuration files or the system registry. If this technique is publicly documented as part of the configuration state of the application, system administrators or sophisticated users could then reconfigure an application to use an alternative strategy for finding objects that may or may not have been anticipated by the original application developer. For example, a moniker that supports load balancing could be reconfigured to use a different policy for selecting host machines simply by changing the textual version of the moniker stored in an application’s configuration file.

The textual representation of a moniker is formally called a display name. The IMoniker interface exposes a method, GetDisplayName, that allows clients to ask a moniker for its display name. The more interesting problem is how one resolves arbitrary display names onto monikers. This task is somewhat problematic, as the client cannot easily tell what kind of moniker a display name corresponds to. That is the job of MkParseDisplayName, arguably the most important API function in all of COM.

MkParseDisplayName takes an arbitrary display name and resolves it to a moniker:

HRESULT MkParseDisplayName(
        [in] IBindCtx *pbc,        // binding info
[in, string] const OLECHAR *pwszName, // object name
       [out] ULONG *pcchEaten,    // progress on error
       [out] IMoniker **ppmk);    // the resultant moniker

The moniker namespace is extensible to support new types of monikers. The top-level parser used by MkParseDisplayName examines the prefix of the display name and tries to match the prefix to a registered ProgID that designates what type of moniker this display name corresponds to. If a match is found, a new moniker of the appropriate type is created and the moniker is given the display name to parse. Because monikers are hierarchical and support composition, the resultant moniker may actually be a composite of two or more monikers. This is an implementation detail that is of no concern to the client. The client simply uses the resultant IMoniker interface pointer (which may or may not point to a composite moniker) to find the object in question.

Recall that the initial entry point into a COM class is through its class object. To connect to a class object, the moniker type that is needed is a Class Moniker. Class Monikers are a built-in moniker type that is provided by COM. Class Monikers keep a CLSID as their internal state and can be created either by using the explicit COM API function CreateClassMoniker:

HRESULT CreateClassMoniker([in] REFCLSID rclsid,
                         [out] IMoniker **ppmk);

or by passing the display name form of the Class Moniker to MkParseDisplayName:11

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

Note that the prefix “clsid” is the ProgID for the Class Moniker.

The following code demonstrates using MkParseDisplayName to create a Class Moniker, which is then used to connect to the class object for Gorillas:

HRESULT GetGorillaClass(IApeClass * &rpgc) {
  rpgc = 0;
// declare the CLSID for Gorilla as a display name
  const OLECHAR pwsz[] = 
   OLESTR("clsid:571F1680-CC83-11d0-8C48-0080C73925BA:");
// create a new binding context for parsing and binding
// the moniker
  IBindCtx *pbc = 0;
  HRESULT hr = CreateBindCtx(0, &pbc);
  if (SUCCEEDED(hr)) {
    ULONG cchEaten;
    IMoniker *pmk = 0;
// ask COM to convert the display name to a moniker object
    hr = MkParseDisplayName(pbc, pwsz, &cchEaten, &pmk);
    if (SUCCEEDED(hr)) {
// ask the moniker to find or create the object that it 
// refers to
      hr = pmk->BindToObject(pbc, 0, IID_IApeClass,
                         (void**)&rpgc);
// we now have a pointer to the desired object, so release 
// the moniker and the binding context
      pmk->Release();
    }
    pbc->Release();
  }
  return hr;
}

The binding context that is passed to both MkParseDisplayName and IMoniker::BindToObject is simply a helper object that allows auxiliary parameters to be passed to the moniker parsing and binding machinery. For this simple example, all that is needed is a new binding context to act as a placeholder, which is acquired by calling the COM API function CreateBindCtx.12

Windows NT 4.0 introduced an API function that simplifies calling MkParseDisplayName and IMoniker::BindToObject:

HRESULT CoGetObject(
          [in, string] const OLECHAR *pszName, 
          [in, unique] BIND_OPTS *pBindOptions, 
          [in] REFIID riid, 
          [out, iid_is(riid)] void **ppv);

This API function is implemented as follows:

// pseudo-code from OLE32.DLL
HRESULT CoGetObject(const OLECHAR *pszName, BIND_OPTS *pOpt,
                   REFIID riid, void **ppv) {
// prepare for failure
  *ppv = 0;
// create a bind context
  IBindCtx *pbc = 0;
  HRESULT hr = CreateBindCtx(0, &pbc);
  if (SUCCEEDED(hr)) {
// set bind options if provided
    if (pOpt)
      hr = pbc->SetBindOptions(pOpt);
    if (SUCCEEDED(hr)) {
// convert the display name into a moniker
      ULONG cch;
      IMoniker *pmk = 0;
      hr = MkParseDisplayName(pbc, pszName, &cch, &pmk);
      if (SUCCEEDED(hr)) {
// ask the moniker to bind to the named object
        hr = pmk->BindToObject(pbc, 0, riid, ppv);
        pmk->Release();
      }
    }
    pbc->Release();
  }
  return hr;
}

Given this function, creating new gorillas is now a simple matter of finding the class object and invoking the CreateInstance method:

HRESULT CreateAGorillaAndEatBanana() {
  IClassFactory *pcf = 0;
// declare the CLSID for Gorilla as a display name
  const OLECHAR pwsz[] = 
   OLESTR("clsid:571F1680-CC83-11d0-8C48-0080C73925BA:");
// find the class object via the gorilla’s class moniker
  HRESULT hr = CoGetObject(pwsz, 0, IID_IClassFactory, 
                        (void**)&pcf);
  if (SUCCEEDED(hr)) {
    IApe *pApe = 0;
// use the class object to create a gorilla
    hr = pcf->CreateInstance(0, IID_IApe, (void**)&pApe);
    if (SUCCEEDED(hr)) {

Figure 3.5 Activation Using the Class Moniker

// tell the new gorilla to eat a banana
      hr = pApe->EatBanana();
      pApe->Release();
    }
    pcf->Release();
  }
  return hr;
}

Figure 3.5 illustrates which objects are created or found by each operation.

Visual Basic exposes the functionality of the CoGetObject API routine via the intrinsic function GetObject. The following Visual Basic code also creates a new Gorilla and tells it to eat a banana:

Sub CreateGorillaAndEatBanana()
  Dim gc as IApeClass
  Dim ape as IApe
  Dim sz as String
  sz = "clsid:571F1680-CC83-11d0-8C48-0080C73925BA:"
' get the class object for gorillas
  Set gc = GetObject(sz)
' ask gorilla class object to create a new gorilla
  Set ape = gc.CreateApe()
' ask gorilla to eat a banana
  ape.EatBanana
End Sub

Note that the Visual Basic version of this function uses the IApeClass interface to instantiate the object. This is because Visual Basic cannot use the IClassFactory interface due to restrictions of the language.

11 Although using MkParseDisplayName will be somewhat less efficient, it allows much more flexibility. As noted previously, the display name could be read from a file or even from the user interface. Microsoft’s Internet Explorer is a great example of an application that allows users to type in arbitrary object names (URLs) that are resolved to monikers (using the extended API function, MkParseDisplayNameEx).

12 Bind contexts are used by composite monikers to optimize parsing and binding operations. Bind contexts also allow clients to specify CLSCTX flags as well as a COSERVERINFO, although the current implementation of the Class Moniker will ignore both of these attributes. The Class Moniker instead assumes that it will be composed with a moniker that references an implementation of the IClassActivator interface, which provides much greater flexibility.

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