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.
- Create a new dialog-based executable using the MFC AppWizard and call it
EffectBrowser
- On the main dialog, add a list control and name it
IDC_EFFECTS
. Add a corresponding CListBox member and name it m_Effects
;
- As our app is a COM client, we need to initialise COM in
CEffectBrowserApp
's InitInstance()
function. For example: if (CoInitialise(NULL) == SUCCEEDED)
{
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.
- Create a new ATL Server application called
Effects
.
- 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
.
- 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.
- 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:
- Create another ATL Server and call it
EffectMosaic
.
- Add an ATL object and use the short name
Effect
. Note that the interface name is IEffect
.
- Right click on the class and select "Implement Interface".
- From the dialog, browse to the
Effects.tlb
type library in your Effects ATL Server project directory and select it.
- 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:
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
- 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).
- Place the new GUID in a header file
EffectsCategory.h
and name the category CATID_EFFECTS
. Here's an example:
static const GUID CATID_EFFECTS =
{ 0x66ebc7c5, 0xa3d0, 0x47c4, { 0x97, 0x9a, 0x56, 0xd1, 0xc3, 0x4f,
0x15, 0x7b } };
Now we just need to establish our custom category map in each of our effect components:
- 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()
- 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:
- The EffectCategories.h header file that contains the category GUID
- 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.
- From ClassWizard, add a function to handle double-clicking the
IDC_EFFECTS
list-box.
- 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,
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
- geo_m - CodeProject member's valuable assistance for helping me understand the syntax of COM inheritance.
- Len Holgate's "Writing Extensible Applications" article on CodeProject
- Developer's Workshop to COM & ATL 3.0 by Andrew W. Troelsen - A great intro to COM and highly recommended!