编写可扩展的 COM 应用程序





4.00/5 (9投票s)
2003年3月7日
7分钟阅读

74273

1591
使用组件分类和接口继承编写可扩展的 COM 应用程序
引言
COM 以其复杂性和难以掌握而闻名,但 Visual Studio 的向导实际上可以非常轻松地创建您自己的 COM 服务器,并且有大量的示例展示如何编写简单的组件。本文无意成为 COM 的教程。它旨在演示 COM 的接口继承和组件分类的一个实际应用——创建一个可由您或其他人扩展的应用程序。
我们将要创建的应用程序包含一个简单的对话框,该对话框列出了可用的图形效果,例如模糊、高亮、灰度转换等。该对话框是一个假想图形应用程序的一部分,其理念是用户可以选择一种效果来应用于图像。该应用程序必须是可扩展的,因为我们希望我们的对话框在运行时“发现”可用的效果。这种行为允许我们将效果实现为“插件”,以便以后可以添加其他效果。通常,我们可以将我们的应用程序与一些基本效果一起发布,然后在稍后发布其他效果。
背景
本文是我的第一篇,所以我想我最好直接深入研究 COM。这是在我阅读了 Len Holgate 在 CodeProject 上的文章“编写可扩展的应用程序”之后出现的。
使用代码
代码包含三个 Microsoft Visual C++ 6.0 项目
Effects
- 包含我们的可插入效果组件将继承的IEffects
接口。EffectBrowser
- 一个 COM 客户端应用程序,它显示一个对话框,其中包含一个列表控件,列出所有可用的效果。EffectMosaic
- 继承自 IEffects 接口,并且是可在运行时被 Effect Browser 应用程序“发现”的对象的一个示例。
还有一套可用的二进制文件:应用程序和两个 DLL。如果您现在不想构建代码,则可以尝试一下。只需使用 regsvr32.exe
(应随 Visual C++ 提供)注册 DLL,然后运行应用程序,它应该会列出 Mosaic Effect 组件的 ProgID。
创建应用程序
我们只需要一个简单的应用程序,其中包含一个包含单个列表框控件的单个对话框。
- 使用 MFC AppWizard 创建一个基于对话框的新可执行文件,并将其命名为
EffectBrowser
。 - 在主对话框上,添加一个列表控件并命名为
IDC_EFFECTS
。添加一个相应的 CListBox 成员并命名为m_Effects
。 - 由于我们的应用程序是一个 COM 客户端,我们需要在
CEffectBrowserApp
的InitInstance()
函数中初始化 COM。例如:if (CoInitialise(NULL) == SUCCEEDED) { // Display the dialog CEffectBrowserDlg dlg; dlg.DoModal(); CoUninitialise(); }
现在可以构建该应用程序了。
创建用于继承的 COM 服务器
接下来,我们必须声明一个所有插件都可以继承的接口。就我们的示例而言,我们将为接口创建一个单独的 COM 服务器。
- 创建一个名为
Effects
的新 ATL Server 应用程序。 - 使用类向导添加一个类,并将类类型声明为 ATL。选择“custom interface”并输入名称
CEffects
。请注意,您的接口名称现在将是IEffects
。 - 通过右键单击
IEffects
接口并选择“Add Method”来添加一个方法,并将其命名为GetName
。将参数指定如下:[out, retval] BSTR *retval.
- 构建您的 ATL Server 并注意已创建一个类型库
Effects.tlb
。稍后我们将将其导入到我们的插件中。
创建插件
现在让我们创建一个 Effect 插件。
- 创建另一个 ATL Server 并将其命名为
EffectMosaic
。 - 添加一个 ATL 对象,并使用短名称
Effect
。请注意,接口名称是IEffect
。 - 右键单击类并选择“Implement Interface”。
- 从对话框中,浏览到 Effects ATL Server 项目目录中的
Effects.tlb
类型库并选择它。 - 选择
IEffects
复选框,然后单击 OK。
此时,您已使用导入的类型库继承了 IEffects
接口,如果您查看 Effect.h
头文件,您会发现以下代码:
//IEffect STDMETHODIMP HRESULT GetName(BSTR *retval) { return S_OK; }
这是向导生成的 IEffect
的 GetName()
方法的实现。您可以更改它以返回有用的内容。以下代码将效果的名称返回给组件的用户。在我们的示例中,它允许我们的主应用程序在列表框中显示组件的名称。
CComBSTR str = "Mosaic"; str.CopyStr(retval); return S_OK;
使用自定义组件类别
我们必须对组件进行分组,以便在运行时被我们的应用程序找到。为此,我们将指定一个 COM 类别,我们的效果组件属于该类别。COM 类别由 GUID(难道不是一切都一样吗?)唯一标识,称为类别 ID 或 CATID。可以想象,有许多预定义的类别,但我们将定义一个自定义类别。顺便说一句,您可以在注册表中查看类别信息,位于以下键下:
HKEY_CLASSES_ROOT/Component Categories
- 首先,我们需要为我们的自定义类别定义一个 GUID,我们使用 GUID 生成工具
guidgen.exe
(应由 Visual Studio 提供)来完成此操作。 - 将新 GUID 放入一个名为
EffectsCategory.h
的头文件中,并将类别命名为CATID_EFFECTS
。以下是一个示例:
// {66EBC7C5-A3D0-47c4-979A-56D1C34F157B} static const GUID CATID_EFFECTS = { 0x66ebc7c5, 0xa3d0, 0x47c4, { 0x97, 0x9a, 0x56, 0xd1, 0xc3, 0x4f,
0x15, 0x7b } };
现在我们只需在我们的每个效果组件中建立自定义类别映射。
- 在 Mosaic 组件的
Effect.h
头文件中,在对象映射部分下添加以下代码,并记住包含EffectsCategory.h
头文件!BEGIN_CATEGORY_MAP(CEffect) IMPLEMENTED_CATEGORY(CATID_EFFECTS) END_CATEGORY_MAP()
- 重新构建您的组件,然后在注册表中查看它是否存在。
修改应用程序
接下来,我们必须修改我们的主应用程序以使用 Category Manager 在运行时获取我们新分类的组件。EffectBrowerDlg.cpp
文件必须包含以下文件:
- 包含类别 GUID 的 EffectCategories.h 头文件。
- COM 服务器中插件继承其接口的两个生成文件:
Effects_i.c
包含 IIDs 和 CLSIDs,Effects.h
包含接口的定义。
在您的主应用程序中,将以下代码添加到您的对话框初始化方法中:
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();
上面的代码从 Component Category Manager 检索类别信息。它查找实现我们类别的任何 CLSID(类标识符),并将它们转换为 ProgID,然后可以显示在我们的应用程序的列表框中。ProgID 提供 CLSID 的“人类可读”版本。
好了,现在构建您的应用程序并运行它。对话框现在应该会显示 Mosaic Effect 组件的 ProgID。
现在我们只需要添加一些代码来调用与所选 ProgID 关联的 GetName()
方法。
- 从 ClassWizard 中,添加一个函数来处理对
IDC_EFFECTS
列表框的双击。 - 在
EffectBrowserDlg.cpp
中,包含Atlbase.h
头文件以支持CComBSTR
的使用,这是一个方便的BSTR
类型包装器类。
将以下代码添加到双击处理程序中:
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(); }
上面的代码获取列表框选择(ProgID),并将其转换为 CLSID。然后创建一个由该 CLSID 标识的组件实例,并调用其 GetName()
方法。然后将返回的组件名称显示在消息框中。
构建应用程序,然后在列表框中双击 ProgID。您应该会收到一个包含相应组件名称的消息框。
尝试以相同的方式创建更多效果组件。
摘要
这是使用“可插入”COM 组件编写可扩展应用程序的一个示例。
我们的应用程序动态发现属于我们定义的自定义 COM 类别的所有可用效果组件的 CLSID。使用这些 CLSID,它将相应的 ProgID 填充到列表框中。选择 ProgID 后,会调用相应的 GetName()
方法,并显示一个包含组件名称的消息。GetName()
方法由每个组件实现,这是继承的 IEffects
接口合同的要求。因此,应用程序(COM 客户端)可以使用 IEffects
接口调用每个组件的方法。
致谢和参考
- geo_m - CodeProject 会员宝贵的帮助,帮助我理解 COM 继承的语法。
- Len Holgate 在 CodeProject 上的“编写可扩展的应用程序”一文。
- Andrew W. Troelsen 的《Developer's Workshop to COM & ATL 3.0》——一本很棒的 COM 入门书,强烈推荐!