Click here to Skip to main content
15,868,051 members
Articles / Desktop Programming / MFC
Article

Writing An Extensible COM Application

Rate me:
Please Sign up or sign in to vote.
4.00/5 (10 votes)
6 Mar 20037 min read 73K   1.6K   43   6
Writing extensible COM applications using component categorisation and interface inheritance

Effect Browser

Introduction

COM's known for being complex and difficult to get to grips with but Visual Studio's wizards actually make it very easy to create your own COM servers and there's plenty of examples around showing how to write simple components. This article does not attempt to be a tutorial on COM. It is intended to demonstrate a practical application of COM's interface inheritance and component categorisation - to create an application that can be extended by you or others.

The application we're going to create consists of a simple dialog that lists available graphic effects such as blurring, highlighting, greyscale conversion etc. The dialog is part of an imaginary graphics application and the idea is that a user can select an effect to apply to an image. The application must be extensible because we want our dialog to "discover" available effects at run-time. This behaviour allows us to implement our effects as "plugins" so that additional effects can be added at a later date. Typically, we could then ship our application with a number of basic effects and ship other effects later on.

Background

This article is my first so I thought I may as well dive in the deep-end and make it about COM. It came about after reading Len Holgate's article, "Writing Extensible Applications" article on CodeProject.

Using the code

The code consists of three Microsoft Visual C++ 6.0 projects:

  • Effects - Contains the IEffects interface that our pluggable effect components will inherit from.
  • EffectBrowser - A COM client applicaiton which displays a dialog box containing a list control which lists all available effects.
  • EffectMosaic - Inherits from the IEffects interface and is an example of an object which can be "discovered" by the Effect Browser application at run-time.

There's also a set of binaries available: the application and two DLLs. If you don't want to build the code right now, you can try them out instead. Just register the DLLs using regsvr32.exe (should be provided with Visual C++). then run the application and it should list the Mosaic Effect component's ProgID.

Creating the Application

We just need a simple application with a single dialog box containing a single list box control.

  1. Create a new dialog-based executable using the MFC AppWizard and call it EffectBrowser
  2. On the main dialog, add a list control and name it IDC_EFFECTS. Add a corresponding CListBox member and name it m_Effects;
  3. As our app is a COM client, we need to initialise COM in CEffectBrowserApp's InitInstance() function. For example:
    if (CoInitialise(NULL) == SUCCEEDED)
    {
       // Display the dialog
       CEffectBrowserDlg dlg;
       dlg.DoModal();
       CoUninitialise();
    }

The application can now be built.

Creating a COM Server to Inherit From

Next, we have to declare an interface which all of our plug-ins can inherit from. For the purpose of our example, we'll create a separate COM server for our interface.

  1. Create a new ATL Server application called Effects.
  2. Add a class using the class wizard and declare the class type as ATL. Select "custom interface" and enter a name CEffects. Note that your interface name will now be IEffects.
  3. Add a method to your IEffects interface (by right-clicking on it) and call it GetName. Specify the parameters as follows: [out, retval] BSTR *retval.
  4. Build your ATL Server and note that a type library has been created, Effects.tlb. We'll be importing that into our plug-ins later.

Creating a Plugin

Now let's create an Effect plug-in:

  1. Create another ATL Server and call it EffectMosaic.
  2. Add an ATL object and use the short name Effect. Note that the interface name is IEffect.
  3. Right click on the class and select "Implement Interface".
  4. From the dialog, browse to the Effects.tlb type library in your Effects ATL Server project directory and select it.
  5. Select the IEffects selection box and click OK

At this point you've used the imported type library to inherit the IEFfects interface and if you take a look in your Effect.h header file you'll find the following code:

//IEffect
STDMETHODIMP HRESULT GetName(BSTR *retval)
{
   return S_OK;
}

This is the wizard-generated implementation of IEffect's GetName() method. You can change it to return something useful. The following code returns the name of the effect to the user of your component. In our example, it allows our main application to display the component's name in the list box.

CComBSTR str = "Mosaic";
str.CopyStr(retval);
return S_OK;

Using a Custom Component Category

We have to group our components so that they can be found at run-time by our application. To do this we'll specify a COM category to which our effect components belong. A COM category is uniquely identified by a GUID (isn't everything?), and is known as a category ID or CATID. There's a number of predefined categories as you can imagine, but we're going to define a custom category. As a matter of interest, you can view category information in the registry under the following key:

HKEY_CLASSES_ROOT/Component Categories
  1. First of all we need to define a GUID for our custom category and we do this using GUID generation tool guidgen.exe (should be provided by Visual Studio).
  2. Place the new GUID in a header file EffectsCategory.h and name the category CATID_EFFECTS. Here's an example:
// {66EBC7C5-A3D0-47c4-979A-56D1C34F157B}
static const GUID CATID_EFFECTS =
{ 0x66ebc7c5, 0xa3d0, 0x47c4, { 0x97, 0x9a, 0x56, 0xd1, 0xc3, 0x4f, <BR>                                0x15, 0x7b } };

Now we just need to establish our custom category map in each of our effect components:

  1. In the Mosaic component's Effect.h header file, add the following code under the object map section and remember to include the EffectsCategory.h header!
    BEGIN_CATEGORY_MAP(CEffect)
       IMPLEMENTED_CATEGORY(CATID_EFFECTS)
    END_CATEGORY_MAP()
  2. Rebuild your component and have a look in the registry to see that it's there.

Modifying the Application

Next, we have to modify our main application to obtain our newly categorised components at runtime using the Category Manager. The EffectBrowerDlg.cpp file must include the following files:

  1. The EffectCategories.h header file that contains the category GUID
  2. Two generated files in the COM server our plug-ins inherit their interface: Effects_i.c contains the IIDs and CLSIDs and Effects.h contains the definitions for the interfaces.

In your main application, add the following code to your dialog box Initialise method:

CATID catid = CATID_EFFECTS;
CLSID clsid[40];
LPOLESTR progID;
ICatInformation *pCatInfo = NULL;

HRESULT hr = CoCreateInstance(CLSID_StdComponentCategoriesMgr, 0,
   CLSCTX_SERVER, IID_ICatInformation, (void **)&pCatInfo);
if (SUCCEEDED(hr))
{
   IEnumCLSID *pCLSID = NULL;
   CATID catids[1];
   catids[0] = catid;
   hr = pCatInfo->EnumClassesOfCategories(1, catids, -1, 0, &pCLSID);
   do
   {
      DWORD num = 0;
      hr = pCLSID->Next(20, clsid, &num);
      if (SUCCEEDED(hr))
      {
         for (DWORD i = 0; i < num; i++)
         {
            ProgIDFromCLSID(clsid[i], &progID);
            char buf[40];
            WideCharToMultiByte(CP_ACP, NULL, progID, -1, buf, 40, NULL, NULL);
            m_Effects.AddString((CString)buf);
         }
      }
   } while (hr == S_OK);
   pCLSID->Release();
}
pCatInfo->Release();

The code above retrieves category information from the Component Category Manager. It finds any CLSIDs (class identifiers) implementing our category and converts them to ProgIDs which can then be displayed in our application's list box. ProgIDs present a "human-readable" version of the CLSID.

OK, now build your application and run it. The dialog box should now display the Mosaic Effect component's ProgID.

Now we just need to add some code to call the GetName() method associated with the selected ProgID.

  1. From ClassWizard, add a function to handle double-clicking the IDC_EFFECTS list-box.
  2. In EffectBrowserDlg.cpp, include the Atlbase.h header file to support the use of CComBSTR, a convenient wrapper class for the BSTR type.

Add the following code to the double-click handler:

CString sProgID;
WCHAR wszProgID[40];
CLSID clsid;

int index = m_Effects.GetCurSel();
m_Effects.GetText(index, sProgID);
MultiByteToWideChar(CP_ACP, 0, sProgID, sProgID.GetLength() + 1, wszProgID, 40);
CLSIDFromProgID(wszProgID, &clsid);
IEffects *pMyEffect = NULL;

HRESULT hr = CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER,<BR>                              IID_IEffects, (void **)&pMyEffect);
if (SUCCEEDED(hr))
{
   CComBSTR serverName;
   pMyEffect->GetName(&serverName);
   CString str;
   str.Format("Component name is %s", (CString)serverName);
   AfxMessageBox(str);
   pMyEffect->Release();
}

The code above gets the list box selection, a ProgID, and converts it to a CLSID. An instance of the component identified by this CLSID is then created and its GetName() method is called. The returned component name is then displayed in a message box.

Build the application and double-click on the ProgID in the list-box. You should get a message box containing the corresponding name of the component.

Try creating more effect components in the same way.

Summary

This is an example of how to write an extensible application using "pluggable" COM components.

Our application dynamically discovers the CLSIDs of all available effect components belonging to a custom COM category that we defined. Using these CLSIDs, it populates a listbox with the corresponding ProgIDs. On selecting a ProgID, the corresponding GetName() method is called and a message is displayed containing the name of the component. The GetName() method is implemented by each component as the contract with the inherited IEffects interface requires. As such, the application, a COM client, can call each component's method(s) using the IEffects interface.

Credits & References

  1. geo_m - CodeProject member's valuable assistance for helping me understand the syntax of COM inheritance.
  2. Len Holgate's "Writing Extensible Applications" article on CodeProject
  3. Developer's Workshop to COM & ATL 3.0 by Andrew W. Troelsen - A great intro to COM and highly recommended!

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 Kingdom United Kingdom
I'm a self-employed IT Consultant specialising in software engineering and web application development. My experience includes .NET, ASP, C#/C/C++/Java/VB on Windows and UNIX platforms having started off as a games programmer working in 68000 assembler on the Atari ST and Commodore Amiga. My main hobbies are playing classical piano and tempting fate on my Kawasaki ZX9R!

Comments and Discussions

 
GeneralToolbars and Menus Pin
thomas_tom999-Sep-04 2:05
thomas_tom999-Sep-04 2:05 
Generalyou just beat me to it.... Pin
Amit Dey12-Mar-03 13:37
Amit Dey12-Mar-03 13:37 
GeneralNice work Pin
Garth J Lancaster11-Mar-03 10:18
professionalGarth J Lancaster11-Mar-03 10:18 
GeneralprogID leak Pin
Vlad Vissoultchev7-Mar-03 4:41
Vlad Vissoultchev7-Mar-03 4:41 
GeneralRe: progID leak Pin
dog_spawn7-Mar-03 6:16
dog_spawn7-Mar-03 6:16 
GeneralRe: progID leak Pin
Garth J Lancaster11-Mar-03 10:16
professionalGarth J Lancaster11-Mar-03 10:16 
Well 'dog-spawn' & Vlad - your comments may be just - it is a great article, specially for guys like me who are just beginning this stuff, and maybe he should now write it in ATL & COM smart pointers ....

.. but you guys could obviously easily put together the same with ATL & Smart pointers, so why dont you take exactely the same content he has and write a contrasting article - it would be great to see both sides !!!

Im not really picking or criticising - just Ive learned it takes a certain amount of time and I guess 'guts' to post an article, but to hammer it down without providing proof takes no time and no guts

so how about the same thing in ATL and Smart Pointers ??

'G'

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.