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 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...
If you also need to register the category, the above becomes...
catMgr.RegisterCategory(myCATID, _T("This is a category"));
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!
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...
CIterateGUID start = catMgr.IterateClassesOfCategory(myCATID);
CIterateGUID end = CIterateGUID::End();
for (CIterateGUID &it = begin; it != end; ++it)
if (S_OK == StringFromIID(it, &lpGUIDString))
std::wcout << L" "<< lpGUIDString << std::endl;
Compare this code to that found in the IEnum sample that doesn't use the Component Category Manager.
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);
IEnumCATID *pIEnumCatidClone = 0;
hr = pIEnumCatid->Clone(&pIEnumCatidClone);
pIEnumCatidClone->Release(); // Doesn't matter which order these are
// the second release causes an access
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().
So that this article and code can be kept up to date more easily I've provided a link to where the article is located on my own web pages. Read the full article and download the source code.