![]() |
Platforms, Frameworks & Libraries »
COM / COM+ »
General
Intermediate
Writing An Extensible COM ApplicationBy thowraWriting extensible COM applications using component categorisation and interface inheritance |
VC6, VC7Win2K, WinXP, MFC, COM, Dev
|
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||

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.
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.
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.
We just need a simple application with a single dialog box containing a single list box control.
EffectBrowser
IDC_EFFECTS. Add a corresponding CListBox member and name it m_Effects;
CEffectBrowserApp's InitInstance() function. For example: if (CoInitialise(NULL) == SUCCEEDED) { // Display the dialog CEffectBrowserDlg dlg; dlg.DoModal(); CoUninitialise(); }
The application can now be built.
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.
Effects.
CEffects. Note that your interface name will now be IEffects.
IEffects interface (by right-clicking on it) and call it GetName. Specify the parameters as follows: [out, retval] BSTR *retval.
Effects.tlb. We'll be importing that into our plug-ins later. Now let's create an Effect plug-in:
EffectMosaic.
Effect. Note that the interface name is IEffect.
Effects.tlb type library in your Effects ATL Server project directory and select it.
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;
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
guidgen.exe (should be provided by Visual Studio).
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,
0x15, 0x7b } };
Now we just need to establish our custom category map in each of our effect components:
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()
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:
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.
IDC_EFFECTS list-box.
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.
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.
| You must Sign In to use this message board. | |||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 6 Mar 2003 Editor: Chris Maunder |
Copyright 2003 by thowra Everything else Copyright © CodeProject, 1999-2009 Web17 | Advertise on the Code Project |