Download source files - 38 Kb
Download sample application - 113 Kb
The Problem
COM objects can be used to make an application easy to extend. Part of this extensibility
comes from being able to write many COM objects that all conform to an interface. The
application uses the interface and doesn't care who implements it. The user should be
able to select the actual object that they want to do the work from a list of objects
which could be used... Luckilly the standard Component Category Manager gives you the
ability to group like COM objects together in a category and then easily manipulate
them. The problem is that it's exposed as a series of slightly grungy interfaces...
A Solution
I like solving a problem once, wrapping up that solution into an easy to use package
and then using the package as often as is appropriate... When I started working with the
Component Category Manager I realised that there was a lot of boiler-plate code that I was
writing over and over again. I decided to wrap it all up in a class or two and make it all
a little nicer.
There are two interfaces to the Component Category Manager: the registration interface
and the information interface. The registration interface is used by COM objects during
their registration, or by setup programs to register an object as belonging to, or
requiring a particular category. The information interface is then used by the application
needing to use a category of objects. I wrote two classes to wrap these interfaces up.
CComCatRegister
CComCatRegister
wraps an
ICatRegister
interface and provides a
thin wrapper around the standard functionality. The advantages of using the wrapper class
are that for the simplest registration requirements you can just instantiate it and call
a single function to register your object...
CComCatRegister catMgr;
catMgr.RegisterClassImplCategories(myGUID, myCATID);
If you also need to register the category, the above becomes...
CComCatRegister catMgr;
catMgr.RegisterCategory(myCATID, _T("This is a category"));
catMgr.RegisterClassImplCategories(myGUID, myCATID);
You can also register the class as belonging to multiple categories, or register
categories that the class requires, rather than implements.
That's about all there is to using the registration category manager. Compare this to
the boiler-plate code required to initialise COM, get an ICatRegister
interface, manage its lifetime, provide the locale ID for the category descriptions,
etc, etc... It's easier!
CComCatInformation
The second Component Category Manager interface is
ICatInformation
.
This is used by applications that want to discover which objects belong to which
categories, which categories an object requires, which categories it implements, etc.
As with CComCatRegister
, CComCatInformation
wraps the standard
COM interface in a thin wrapper. This class adds more value that CComCatRegister
as the underlying interface is more complex. Using the IEnumXXXX
iterator
wrappers that are explained
here it neatly
wraps all of the IEnum
interfaces available from ICatInformation
and makes them easier to use.
If you wanted to display a list of objects implementing a particular category then all
you need do is something like this...
CComCatInformation catMgr;
CIterateGUID start = catMgr.IterateClassesOfCategory(myCATID);
CIterateGUID end = CIterateGUID::End();
for (CIterateGUID &it = begin; it != end; ++it)
{
LPOLESTR lpGUIDString;
if (S_OK == StringFromIID(it, &lpGUIDString))
{
std::wcout << L" " << lpGUIDString << std::endl;
CoTaskMemFree(lpGUIDString);
}
}
Compare this code to that found in the
IEnum
sample that doesn't use the Component Category Manager.
Wrapping up
These wrapper classes make the Component Category Manager easier to use. It's worth
using it as it makes your applications more easily extendable. Never tie your application
to a single instance of an object when you could, instead, make it dependant on a category
of objects that perform the task it requires. You can then allow the user to change the
actual object with some very simple code.
One thing that confuses me...
Though the Component Category Manager is, undoubtably, a Good Thing, there is one
aspect of it that confuses me. If you look in the registry, each object that is in a
category lists the category under its registry key. This makes it easy to determine if the
object is in the category. However, there appears to be no list of "objects that are
in a category" which implies that to find a list of all objects that are in a
particular category the Component Category Manager has to look at every object in the
system... This seems odd... But then, perhaps I'm missing something.
A bug in the Component Category Manager?
There appears to be a bug in implementation of
IEnumCATID
that is supplied
with the standard component category manager. I have version 4.71 of
ComCat.dll
on my machine and calling
Clone()
on an
IEnumCATID
interface
pointer which was obained from a call to either
EnumImplCategoriesOfClass()
or
EnumReqCategoriesOfClass()
gives you a pointer which appears to be linked
to the original pointer you called
Clone()
on. Calling
Release()
on
either the cloned pointer or the original appears to invalidate the other... This is
certainly not the case with the other
IEnum
interfaces presented by the
component category manager.
The problem can be seen with the code below (there's a complete test program available
for from the download page).
IEnumCATID *pIEnumCatid = 0;
hr = pICatInfo->EnumImplCategoriesOfClass(guid, &pIEnumCatid);
if (SUCCEEDED(hr))
{
IEnumCATID *pIEnumCatidClone = 0;
hr = pIEnumCatid->Clone(&pIEnumCatidClone);
if (SUCCEEDED(hr))
{
pIEnumCatid->Release();
pIEnumCatidClone->Release();
}
}
Apparantly a new version of the component category manager is available with VB6.0 the
version of ComCat.dll should be 5.0. I would be intestested to know if this bug is still
present in the latest version. Version 5.0 is also supposedly part of IE4sr1 but I have
that installed and still have 4.71
How this affects CComCatInformation
Admittedly,
EnumImplCategoriesOfClass()
and
EnumReqCategoriesOfClass()
are probably the least used functions on the
ICatInformation
interface, and
for most uses you wouldn't need to call
Clone()
on an interface pointer obtained
from them. However, it causes problems with my wrapper class as the iterators are returned
by value and this causes the interface pointer to be
Clone()
d in the copy
constructor of the IEnumIterator...
If the test program fails on your machine, do not use
CComCatInformation::IterateImplCategoriesOfClass()
or
CComCatInformation::IterateReqCategoriesOfClass()
.
See the article on Len's homepage for the latest updates.