Click here to Skip to main content
15,867,686 members
Articles / Desktop Programming / ATL
Article

Pluggable Components using Component Categories Part I

Rate me:
Please Sign up or sign in to vote.
4.96/5 (17 votes)
18 Sep 20034 min read 68.2K   1.1K   56   5
An article on using component categories to create pluggable components

Config Dialog

Blue Component Red Component

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
MIDL
// IComCatDraw - Category Interface Definition
[
    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
MIDL
// 
ComCatBlue.idl : IDL source for ComCatBlue.dll
//

// This file will be processed by the MIDL tool to
// produce the type library (ComCatBlue.tlb) and marshalling code.

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
{
    . . .
// IComCatDrawBlue
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
// ComCatDrawBlue.h 
: Declaration of the CComCatDrawBlue

#ifndef __COMCATDRAWBLUE_H_
#define __COMCATDRAWBLUE_H_

#include "resource.h"       // main symbols

#include "ComCatDrawCategory.h"

//////////////////////////////////////////////////////////////////////////
// CComCatDrawBlue
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()

// IComCatDrawBlue
public:
    STDMETHOD(Draw)(HDC hDC);
};

#endif //__COMCATDRAWBLUE_H_
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<ICATINFORMATION> 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<IENUMCLSID> 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;  // return TRUE unless you set the focus to a control
                  // EXCEPTION: OCX Property Pages should return FALSE
}

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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
United States United States
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.

Comments and Discussions

 
GeneralGreat Article! Pin
badvox23-Jul-03 9:29
sussbadvox23-Jul-03 9:29 
GeneralRe: Great Article! Pin
Zac Howland26-Jul-03 18:26
Zac Howland26-Jul-03 18:26 
Thanks Mike Smile | :)

I'll have to check out that book. I had to pretty much do all of this on my own (got some of the theory and a starting point from a couple other articles and had to implement this on my own).

This article was very basic (in terms of functionality). Part 2 of this should be up soon.

"If I create everything new, why would I want to delete anything?"
GeneralLike Part1...... Pin
Kevin Gutteridge4-Jul-03 9:28
Kevin Gutteridge4-Jul-03 9:28 
GeneralRe: Like Part1...... Pin
Zac Howland14-Jul-03 5:03
Zac Howland14-Jul-03 5:03 
GeneralRe: Like Part1...... Pin
Zac Howland23-Jul-03 5:04
Zac Howland23-Jul-03 5:04 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.