Links
Introduction
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.
Background
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.
ComCatDraw.idl
[
object,
uuid(C49A2274-8D1F-47b9-8476-8250174956EB),
helpstring("IComCatDraw Interface"),
pointer_default(unique)
]
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 IUnknown
(e.g. 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
ComCatBlue.idl : IDL source for ComCatBlue.dll
import "oaidl.idl";
import "ocidl.idl";
import "ComCatDraw.idl";
[
object,
uuid(98639EC3-6C69-490B-BDC2-095ECC133F30),
helpstring("IComCatDrawBlue Interface"),
pointer_default(unique)
]
interface IComCatDrawBlue : IComCatDraw
{
};
[
uuid(669E1E78-D56A-4136-A926-2EC613C10720),
version(1.0),
helpstring("ComCatBlue 1.0 Type Library")
]
library COMCATBLUELib
{
importlib("stdole32.tlb");
importlib("stdole2.tlb");
[
uuid(653BF0E4-3D19-4000-A189-AFA128B7E99B),
helpstring("ComCatDrawBlue Class")
]
coclass ComCatDrawBlue
{
[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:
ComCatDrawBlue.h
class
ATL_NO_VTABLE CComCatDrawBlue
{
. . .
public:
STDMETHOD(Draw)(HDC hDC);
};
Now we can implement this function.
ComCatDrawBlue.cpp
STDMETHODIMP
CComCatDrawBlue::Draw(HDC hDC)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState())
CDC* pDC = CDC::FromHandle(hDC);
CBrush brush;
brush.CreateSolidBrush(RGB(0x00, 0x00, 0xFF));
CBrush* pOldBrush = pDC->SelectObject(&brush);
pDC->RoundRect(CRect(10, 10, 210, 210), CPoint(50, 50));
pDC->SelectObject(pOldBrush);
return S_OK;
}
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.
ComCatDrawCategory.h
{C49A2274-8D1F-47b9-8476-8250174956EB}
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
ComCatDrawBlue.h
: Declaration of the CComCatDrawBlue
#ifndef __COMCATDRAWBLUE_H_
#define __COMCATDRAWBLUE_H_
#include "resource.h" // main symbols
#include "ComCatDrawCategory.h"
class ATL_NO_VTABLE CComCatDrawBlue :
public CComObjectRootEx<CCOMSINGLETHREADMODEL>,
public CComCoClass<CCOMCATDRAWBLUE, &CLSID_ComCatDrawBlue>,
public IComCatDrawBlue
{
public:
CComCatDrawBlue()
{
}
DECLARE_REGISTRY_RESOURCEID(IDR_COMCATDRAWBLUE)
DECLARE_PROTECT_FINAL_CONSTRUCT()
BEGIN_COM_MAP(CComCatDrawBlue)
COM_INTERFACE_ENTRY(IComCatDraw)
COM_INTERFACE_ENTRY(IComCatDrawBlue)
END_COM_MAP()
BEGIN_CATEGORY_MAP(CComCatDrawBlue)
IMPLEMENTED_CATEGORY(CATID_ComCatDrawCategory)
END_CATEGORY_MAP()
public:
STDMETHOD(Draw)(HDC hDC);
};
#endif
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 OnInitDialog
function.
ConfigDlg.cpp
BOOL
CConfigDlg::OnInitDialog()
{
CDialog::OnInitDialog();
CComPtr pInfo;
if (FAILED(CoCreateInstance(CLSID_StdComponentCategoriesMgr, NULL,
CLSCTX_ALL, IID_ICatInformation, (void**)&pInfo)))
return FALSE;
int cIDs = 1;
CATID IDs[1];
IDs[0] = CATID_ComCatDrawCategory;
CComPtr pEnumCLSID = NULL;
if (FAILED(pInfo->EnumClassesOfCategories(cIDs, IDs, 0, NULL,
&pEnumCLSID)))
return FALSE;
char szFriendlyName[128] ;
CLSID clsid ;
while (pEnumCLSID->Next(1, &clsid, NULL) == S_OK)
{
if (getFriendlyName(clsid, szFriendlyName,
sizeof(szFriendlyName)))
{
int index = m_list.AddString(szFriendlyName) ;
CLSID* pclsid = new CLSID ;
*pclsid = clsid ;
m_list.SetItemDataPtr(index, pclsid) ;
}
}
if (m_list.GetCount() > 0)
m_list.SetCurSel(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:
ComCatAppView.cpp
void
CComCatAppView::OnInitialUpdate()
{
CView::OnInitialUpdate();
CConfigDlg dlg;
if (IDOK == dlg.DoModal())
{
m_clsid = dlg.m_clsid;
HRESULT hr = CoCreateInstance(m_clsid, NULL, CLSCTX_INPROC_SERVER,
IID_IComCatDraw, (void**)&m_pDraw);
ASSERT(SUCCEEDED(hr));
}
}
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).
History
- First revision: June 17th, 2003.
- Second revision: September 15th, 2003.