Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Pluggable Components using Component Categories Part I

0.00/5 (No votes)
18 Sep 2003 1  
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
// 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
// 

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 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;  // 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