As this chapter has emphasized, the basic COM activation primitives require the caller to know the precise class name in order to create new instances. However, it is sometimes useful simply to request that any class that adheres to some semantic constraint is suitable. In addition, it could be useful to know what services a class requires from its clients prior to issuing an activation request to avoid creating objects that the client is not prepared to support properly. These problems motivate the concept of component categories.
COM allows implementors to group related COM classes into logical groups or component categories. Often, all classes within a category will implement the same set of interfaces. However, simply partitioning the class space based on which interfaces each class implements does not provide the proper granularity for many applications. Component categories act as metainformation that indicates which classes are compatible with particular semantic constraints.
A component category is a group of logically related COM classes that share a common category ID or CATID. CATIDs are GUIDs that are stored in the Registry as attributes of a class. Each class can have two subkeys: Implemented Categories and Required Categories. Assume that there are two categories of components: Simians and Mammals. These two categories would each have a unique CATID (CATID_Simians and CATID_Mammals, respectively). Assuming that the class Chimp is a member of each of these categories, the Chimp’s Implemented Categories registry key would contain each GUID as an individual subkey:
[HKCR\CLSID\{CLSID_Chimp}\Implemented Categories\{CATID_Mammals}]
[HKCR\CLSID\{CLSID_Chimp}\Implemented Categories\{CATID_Simians}]
These registry entries are typically added at self-registration time. Each known component category on a system has an entry under
HKEY_CLASSES_ROOT\Component Categories
Each category has its own unique subkey named by its CATID. Underneath its subkey, each category has one or more named values that contain the human-readable description of the category. For example, the two categories shown would require the following registry entries:
[HKCR\Component Categories\{CATID_Mammals}]
409="Bears live young"
[HKCR\Component Categories\{CATID_Simians}]
409="Eats Bananas"
Note that this example uses the 409 value, which is the LCID for U.S. English. Other locales can be supported by adding additional named values.
It is also possible for classes to indicate that they require particular types of functionality from the client. This support typically comes in the form of site interfaces that the client will provide to the object once activated. To allow these client-provided services to be categorized independent of any particular interface, COM allows classes to advertise a second type of category ID that can be used by clients to ensure that they do not activate a component that they cannot properly host. Assume the following two categories of client-
provided services: CATID_HasOxygen and CATID_HasWater. Since chimpanzees need oxygen and water to survive, the Chimp implementor might advertise that these two categories of client-provided services are required for activation. This is done by using the Required Categories subkeys:
[HKCR\CLSID\{CLSID_Chimp}\Required Categories\{CATID_HasOxygen}]
[HKCR\CLSID\{CLSID_Chimp}\Required Categories\{CATID_HasWater}]
These two category IDs would need to be registered under
HKEY_CLASSES_ROOT\Component Categories
as well. Given these entries, it is the client’s responsibility to ensure that it meets the required categories prior to activation. COM does not enforce client conformance.
Component category entries can be registered using either explicit registry functions or the COM-supplied component category manager. COM’s component category manager is exposed as an instantiable COM class (CLSID_ StdComponentCategoriesMgr) that implements the ICatRegister interface for registering category information and the ICatInformation interface for querying category information. The ICatRegister interface allows server DLLs easily to add the requisite entries to the Registry:
[ object, uuid(0002E012-0000-0000-C000-000000000046) ]
interface ICatRegister : IUnknown {
// description info for a category
typedef struct tagCATEGORYINFO {
CATID catid;
LCID lcid;
OLECHAR szDescription[128];
} CATEGORYINFO;
// register cCts category descriptions
HRESULT RegisterCategories([in] ULONG cCts,
[in, size_is(cCts)] CATEGORYINFO rgCatInfo[]);
// unregister cCategories category descriptions
HRESULT UnRegisterCategories([in] ULONG cCategories,
[in, size_is(cCategories)] CATID rgcatid[]);
// indicate a class implements one or more categories
HRESULT RegisterClassImplCategories([in] REFCLSID rclsid,
[in] ULONG cCategories,
[in, size_is(cCategories)] CATID rgcatid[]);
// deindicate a class implements with one or more categories
HRESULT UnRegisterClassImplCategories([in] REFCLSID rclsd,
[in] ULONG cCategories,
[in, size_is(cCategories)] CATID rgcatid[]);
// indicate a class requires one or more categories
HRESULT RegisterClassReqCategories([in] REFCLSID rclsid,
[in] ULONG cCategories,
[in, size_is(cCategories)] CATID rgcatid[]);
// deindicate a class requires one or more categories
HRESULT UnRegisterClassReqCategories([in] REFCLSID rclsid,
[in] ULONG cCategories,
[in, size_is(cCategories)] CATID rgcatid[]);
}
There is no need for user-defined COM classes to implement this interface. It exists solely to allow servers to self-register their component categories using the COM-provided category manager implementation.
In the case of the Chimp example, the following code would register the correct information regarding each category:
// get the standard category manager
ICatRegister *pcr = 0;
HRESULT hr = CoCreateInstance(
CLSID_StdComponentCategoriesMgr, 0,
CLSCTX_ALL, IID_ICatRegister, (void**)&pcr);
if (SUCCEEDED(hr)) {
// build descriptions of each category
CATEGORYINFO rgcc[4];
rgcc[0].catid = CATID_Simian;
rgcc[1].catid = CATID_Mammal;
rgcc[2].catid = CATID_HasOxygen;
rgcc[3].catid = CATID_HasWater;
rgcc[0].lcid = rgcc[1].lcid
= rgcc[2].lcid = rgcc[3].lcid = 0x409;
wcscpy(rgcc[0].szDescription, OLESTR("Eats Bananas"));
wcscpy(rgcc[1].szDescription, OLESTR("Bears live young"));
wcscpy(rgcc[2].szDescription, OLESTR("Provides Oxygen"));
wcscpy(rgcc[3].szDescription, OLESTR("Provides Water"));
// register information regarding categories
pcr->RegisterCategories(4, rgcc);
// note that Chimps are Simians and mammals
CATID rgcid[2];
rgcid[0] = CATID_Simian; rgcid[1] = CATID_Mammal;
pcr->RegisterClassImplCategories(CLSID_Chimp, 2, rgcid);
// note that Chimps require Oxygen and Water
rgcid[0] = CATID_HasOxygen; rgcid[1] = CATID_HasWater;
pcr->RegisterClassReqCategories(CLSID_Chimp, 2, rgcid);
pcr->Release();
}
Note that this code makes no raw registry API calls but instead uses the standard category manager to manipulate the Registry.
The standard category manager also allows applications to query the registry to find information regarding categories. This functionality is exposed via the ICatInformation interface:
[ object, uuid(0002E013-0000-0000-C000-000000000046) ]
interface ICatInformation : IUnknown {
// get list of known categories
HRESULT EnumCategories([in] LCID lcid,
[out] IEnumCATEGORYINFO** ppeci);
// get description of a particular category
HRESULT GetCategoryDesc([in] REFCATID rcatid,
[in] LCID lcid,
[out] OLECHAR ** ppszDesc);
// get list of classes compatible with specified categories
HRESULT EnumClassesOfCategories(
[in] ULONG cImplemented, (-1 indicates ignore)
[in,size_is(cImplemented)] CATID rgcatidImpl[],
[in] ULONG cRequired, (-1 indicates ignore)
[in,size_is(cRequired)] CATID rgcatidReq[],
[out] IEnumCLSID** ppenumClsid);
// verify class is compatible with specified categories
HRESULT IsClassOfCategories([in] REFCLSID rclsid,
[in] ULONG cImplemented,
[in,size_is(cImplemented)] CATID rgcatidImpl[],
[in] ULONG cRequired,
[in,size_is(cRequired)] CATID rgcatidReq[]);
// get list of class's implemented categories
HRESULT EnumImplCategoriesOfClass([in] REFCLSID rclsid,
[out] IEnumCATID** ppenumCatid);
// get list of class's required categories
HRESULT EnumReqCategoriesOfClass([in] REFCLSID rclsid,
[out] IEnumCATID** ppenumCatid);
}
Most of these methods return cursors to lists of category or class IDs. These cursors are called enumerators and are described in detail in Chapter 7.
The following code demonstrates how to extract the list of classes that are members of the Mammal category:
// get the standard category manager
ICatInformation *pci = 0;
HRESULT hr = CoCreateInstance(
CLSID_StdComponentCategoriesMgr, 0,
CLSCTX_ALL, IID_ICatInformation, (void**)&pci);
if (SUCCEEDED(hr)) {
// get the classes that are Simians (ignore required cat.s)
IEnumCLSID *pec = 0;
CATID rgcid[1];
rgcid[0] = CATID_Simian;
hr = pci->EnumClassesOfCategories(1, rgcid, -1, 0, &pec);
if (SUCCEEDED(hr)) {
// walk list of CLSIDs 64 at a time
enum { MAX = 64 };
CLSID rgclsid[MAX];
do {
ULONG cActual = 0;
hr = pec->Next(MAX, rgclsid, &cActual);
if (SUCCEEDED(hr)) {
for (ULONG i = 0; i < cActual; i++)
DisplayClass(rgclsid[i]);
}
} while (hr == S_OK);
pec->Release();
}
pci->Release();
}
This code fragment ignores the fact that the client program may not support the required categories of the resultant list of classes. Had the client been aware of its supported site categories, it could have specified the list of all supported categories.
Consider the following call to EnumClassesOfCategories:
CATID rgimpl[1]; rgimpl[0] = CATID_Simians;
CATID rgreq[3]; rgreq[0] = CATID_HasWater;
rgreq[1] = CATID_HasOxygen; rgreq[2] = CATID_HasMilk;
hr =pci->EnumClassesOfCategories(1, rgimpl, 3, rgreq, &pec);
The resultant list of classes would contain all Simians that require no more than Oxygen, Water, and Milk from the client’s environment. In the case of the Chimp class registered previously, Chimp would be a compatible class, as it implements the specified category Simian and requires a subset of the specified categories used in the query.
One final aspect of component categories that bears discussion is the notion of a default class for a category. COM allows a CATID to be registered as a CLSID under
HKEY_CLASSES_ROOT\CLSID
To map a CATID to a default CLSID, the TreatAs facility introduced by emulation is used. To indicate that the Gorilla class is the default class of Simian, one would add the following registry key:
[HKCR\CLSID\{CATID_Simian}\TreatAs]
@={CLSID_Gorrila}
This simple convention allows clients simply to use CATIDs where CLSIDs are expected:
// create an instance of the default Simian class
hr = CoCreateInstance(CATID_Simian, 0, CLSCTX_ALL,
IID_IApe, (void**)&pApe);
If no default class is registered for the specified category, the activation call will fail, returning REGDB_E_CLASSNOTREG.