Recently, I have had a need to write components with similar interfaces and plug one of them into my application at a given time. While there are many ways of doing this, I wanted to use a standard method that was fairly straight forward and easy for me to write. Since, the fundamental concepts behind it fill both of those needs, and it is well-accepted around the industry, I chose COM. I began looking through all my books, and browsing the internet, but I could not find a guide for what I wanted to do. Thanks go to Len Holgate. His two articles (links above) helped me get started.
This is part 1 of this series. In this part, I will demonstrate a very simple interface that is implemented differently in two different COM objects.
I do not have enough time or space to give a complete overview of COM and/or ATL, so I will assume that you are at least familiar with the concepts. This article will focus on what a reusable interface is, what component categories are, and how to use them in your applications.
What is a reusable interface? Well, the idea behind it is that it is an interface that is used by several objects that may implement it in different ways.
interface IComCatDraw : IUnknown
import "unknwn.idl" ;
HRESULT Draw([in] HDC hDC);
This particular example is derived directly from
IUnknown; however, you can derive your interfaces from anything that is indirectly derived from
IDispatch). This is a very simple interface that has only one method (
Draw). We will look at more complex interfaces in part 2. This interface will be the the base interface for our components.
Now, we have a common interface to use. That's great, but now how do we use it? Well, first we need to create a simple COM object and derive its interface from our common interface. I used the ALT COM AppWizard to do this (although, you can do it by hand if you really want to). Here is the derived interface for the blue component:
ComCatBlue.idl : IDL source for ComCatBlue.dll
interface IComCatDrawBlue : IComCatDraw
helpstring("ComCatBlue 1.0 Type Library")
[default] interface IComCatDrawBlue;
Notice the changes. Also, we don't need to declare the
Draw method in the
IComCatDrawBlue interface since it is declared in the base class. We can't compile the project at this point because we have not implemented this function (you will get a linker error if you try). To fix this, you need to add the following to the implementation class:
. . .
Now we can implement this function.
CDC* pDC = CDC::FromHandle(hDC);
brush.CreateSolidBrush(RGB(0x00, 0x00, 0xFF));
CBrush* pOldBrush = pDC->SelectObject(&brush);
pDC->RoundRect(CRect(10, 10, 210, 210), CPoint(50, 50));
We can now successfully compile this project; however, it won't do us much good. All we can do is bind directly to the object, which is what we were trying to get away from. To do this, we need to use GUIDGEN to define a new guid for us.
DEFINE_GUID(CATID_ComCatDrawCategory, 0xc49a2274, 0x8d1f, 0x47b9, 0x84,
0x76, 0x82, 0x50, 0x17, 0x49, 0x56, 0xeb);
This alone doesn't do anything for us. But if we create a category map in the implementation class's header file, the object will be registered as implementing the stated category. This does not necessarily mean anything if used the wrong way. The category registry keys ("Required Categories" and "Implemented Categories") are nothing more than a promise. They state that a particular component either needs an object that implements another category interface, or is implementing a particular category interface. To make this promise, we must add the following code
: Declaration of the CComCatDrawBlue
class ATL_NO_VTABLE CComCatDrawBlue :
public CComCoClass<CCOMCATDRAWBLUE, &CLSID_ComCatDrawBlue>,
That is all there is to it. The Red component was created the exact same way.
Using the code
Now that we have our components, we want to test them. The test application is very simple. It displays a dialog when it starts that shows every object that implements the
IComCatDraw interface that is currently registered on the computer. The user selects one and when the View's
OnDraw function is called, it calls the objects Draw function.
The trick here is to get a list of the components and to get their respective
CLSIDs. This is done in the Config Dialog's
if (FAILED(CoCreateInstance(CLSID_StdComponentCategoriesMgr, NULL,
CLSCTX_ALL, IID_ICatInformation, (void**)&pInfo)))
int cIDs = 1;
IDs = CATID_ComCatDrawCategory;
CComPtr<IENUMCLSID> pEnumCLSID = NULL;
if (FAILED(pInfo->EnumClassesOfCategories(cIDs, IDs, 0, NULL,
char szFriendlyName ;
CLSID clsid ;
while (pEnumCLSID->Next(1, &clsid, NULL) == S_OK)
if (getFriendlyName(clsid, szFriendlyName,
int index = m_list.AddString(szFriendlyName) ;
CLSID* pclsid = new CLSID ;
*pclsid = clsid ;
m_list.SetItemDataPtr(index, pclsid) ;
if (m_list.GetCount() > 0)
return TRUE; }
This uses the Component Category Manager to get the CLSIDs of each component and adds them to a list control. For more detail on the Component Category Manager, see Len Holgate's article.
Once we select the CLSID, it is fairly simple to create the object:
if (IDOK == dlg.DoModal())
m_clsid = dlg.m_clsid;
HRESULT hr = CoCreateInstance(m_clsid, NULL, CLSCTX_INPROC_SERVER,
NOTE: Make sure you include the component category header file (the one that defines the CATID for the category) as well as the header file for the idl file (see the project settings for the ComCatDraw.idl file for details).
Points of Interest
This is a very simple example of how to design and implement component categories to create pluggable objects and use them in an application. In Part 2, we will look at objects that implement connection points.
Also, I only adjusted the configuration settings for the debug configuration. Thus, the Release builds will not work unless you change them to match (specifically, the include files,
midl includes, and the build options for the ComCatDraw.idl file).
- First revision: June 17th, 2003.
- Second revision: September 15th, 2003.
I started programming at 15 with a TI-82 calclator in Z80 assembly (oh, those were the days . . .) I am pretty much a self taught programmer. I've taught myself Visual Basic, C/C++, Java, and am currently working on C#. I also like to experiment with system administration and security issues, and occassionally I work on web design. For the last 4 years, I have worked for Leitch, Inc. as a Software Engineer and graduated from Old Dominion University with bachelor's degrees in Computer Science, Mathematics, and Business Management in December of 2004.