Interface and Implementation Revisited

The previous chapter defined a COM interface as an abstract collection of operations that express one functionality that an object can export. COM interfaces are defined in IDL and have a logical name that indicates the functionality that they model. The following is an IDL definition of the COM interface IApe:

[object,uuid(753A8A7C-A7FF-11d0-8C30-0080C73925BA)]
interface IApe : IUnknown {
  import "unknwn.idl";
  HRESULT EatBanana(void);
  HRESULT SwingFromTree(void);
  [propget] HRESULT Weight([out, retval] long *plbs);
}

The corresponding IApe documentation would specify the rough semantics for the three operations EatBanana, SwingFromTree, and Weight. All objects that expose IApe via QueryInterface must ensure that their implementations of these methods adhere to the semantic contract of IApe. However, interface definitions almost always deliberately leave room for interpretation by object implementors. This means that clients can never be completely certain about the precise behavior of any given method, only that its behavior will follow the rough guidelines described in the interface’s documentation. This controlled degree of uncertainty is a fundamental characteristic of polymorphism and is one of the foundations of object-oriented software development.

Consider the IApe interface just shown. It is possible (in fact, likely) that there will be more than one implementation of the IApe interface. Because the definition of IApe is generic to all implementations, the assumptions that clients can make about the behavior of the EatBanana method must be sufficiently vague to allow gorillas, chimpanzees, and orangutans (all of which might implement the IApe interface) each to have legal (but subtly different) interpretations of this operation. Without this flexibility, polymorphism is impossible.

COM explicitly treats interfaces, implementations, and classes as three distinct concepts. Interfaces are abstract protocols for communicating with an object. Implementations are concrete data types that support one or more interfaces by providing precise semantic interpretations of each of the interface’s abstract operations. Classes are named implementations that represent concrete instantiable types and are formally known as COM classes or coclasses.

In the spirit of encapsulation, all that is known of a COM class is its name and potentially the list of interfaces it exposes. Like COM interfaces, COM classes are named using GUIDs, although when used to name COM classes, GUIDs are called CLSIDs. Like interface names, these class names must be well known to the client prior to using them. Because COM interfaces are semantically vague to allow polymorphism, COM does not allow clients simply to request any available implementation of a given interface. Instead, clients must specify the precise implementation that is desired. This reinforces the fact that COM interfaces are simply abstract communication protocols whose sole purpose is to allow clients to communicate with objects that belong to concrete, meaningful implementation classes.1

In addition to allowing implementations to be named by a CLSID, COM supports text-based aliases called programmatic IDs or ProgIDs. ProgIDs come in the form of libraryname.classname.version and, unlike CLSIDs, are unique only by convention. Clients can map between ProgIDs and CLSIDs using the COM API functions CLSIDFromProgID and ProgIDFromCLSID:

HRESULT CLSIDFromProgID(
           [in, string] const OLECHAR *pwszProgID,
           [out]        CLSID *pclsid);
HRESULT ProgIDFromCLSID(
            [in] REFCLSID rclsid,
            [out, string] OLECHAR **ppwszProgID);

To convert a ProgID to a CLSID, one simply calls CLSIDFromProgID:

HRESULT GetGorillaCLSID(CLSID& rclsid) {
  const OLECHAR wszProgID[] = OLESTR("Apes.Gorilla.1");
  return CLSIDFromProgID(wszProgID, &clsid);
}

The COM runtime will look in its configuration database for a mapping of the ProgID Apes.Gorilla.1 onto a CLSID that corresponds to a COM implementation class.

1 Although it makes little sense to ask for “any available implementation” of a given interface, it sometimes does make sense to have semantic groupings of implementations, all of which share certain high-level traits, such as being an animal or providing a logging service. To support this type of component discovery, COM supports advertising such taxonomies through the use of component categories. While it is often the case that all classes that belong to a given component category will implement a shared set of interfaces, this is by no means a sufficient condition for belonging to a component category.

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