A COM+ object exposes its functionality through interfaces; a client learns about these interfaces through the IUnknown::QueryInterface method. The QueryInterface method has two purposes: to enable clients to determine what interfaces are supported by an object, and to retrieve pointers to those interfaces. The first purpose does not scale well as objects become increasingly complex and begin to support not several but dozens of interfaces. Of course, regardless of the number of interfaces an object supports, QueryInterface is still the method to call when you need an interface pointer.1
As objects and their clients become increasingly complex, they require more functionality from one another. Generally speaking, most COM+ interfaces have between three and seven methods, but legally an interface can have any number of methods. Pragmatically, an interface seems to work best if a programmer can grasp it quickly. As coclasses evolve, they add functionality by implementing new interfaces.
These days, most components need to support a gamut of interfaces that work together to accomplish a single task. Microsoft Visual Basic, for example, requires that an ActiveX control support a dozen or more interfaces. When the user chooses Components from the Project menu in Visual Basic, a list of available ActiveX controls is displayed, as shown in Figure 7-1. Here's the problem: How can client programs such as Visual Basic determine which objects installed on the user's computer support all the interfaces necessary to qualify as ActiveX controls?
Figure 7-1. The Components dialog box in Visual Basic, showing a list of available ActiveX controls.
With only the QueryInterface mechanism at its disposal, Visual Basic would have to traverse the HKEY_CLASSES_ROOT\CLSID section of the registry, instantiate each object, and then call QueryInterface a dozen or more times per object simply to determine whether the necessary interfaces are available. This technique would take an unreasonable amount of time. Ideally, Visual Basic should be able to determine whether a component meets the requirements of its client prior to instantiation.
Component categories make this possible. One solution might be to identify a coclass's functionality by simply listing the interfaces it implements. This might sound like a good approach, but in practice most components belong to a certain type, or category. For example, controls, documents, scripting engines, and Automation objects are different types of components. A component declares its support for one or more categories via registry keys. A client program can then identify and locate the components that support a particular category by a quick scan of the registry. The desired component is then instantiated, and interface pointers are obtained by calling QueryInterface in the usual way.