65.9K
CodeProject 正在变化。 阅读更多。
Home

编写可扩展的 COM 应用程序

starIconstarIconstarIconstarIconemptyStarIcon

4.00/5 (9投票s)

2003年3月7日

7分钟阅读

viewsIcon

74273

downloadIcon

1591

使用组件分类和接口继承编写可扩展的 COM 应用程序

Effect Browser

引言

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。

创建应用程序

我们只需要一个简单的应用程序,其中包含一个包含单个列表框控件的单个对话框。

  1. 使用 MFC AppWizard 创建一个基于对话框的新可执行文件,并将其命名为 EffectBrowser
  2. 在主对话框上,添加一个列表控件并命名为 IDC_EFFECTS。添加一个相应的 CListBox 成员并命名为 m_Effects
  3. 由于我们的应用程序是一个 COM 客户端,我们需要在 CEffectBrowserAppInitInstance() 函数中初始化 COM。例如:
    if (CoInitialise(NULL) == SUCCEEDED)
    {
       // Display the dialog
       CEffectBrowserDlg dlg;
       dlg.DoModal();
       CoUninitialise();
    }
    

现在可以构建该应用程序了。

创建用于继承的 COM 服务器

接下来,我们必须声明一个所有插件都可以继承的接口。就我们的示例而言,我们将为接口创建一个单独的 COM 服务器。

  1. 创建一个名为 Effects 的新 ATL Server 应用程序。
  2. 使用类向导添加一个类,并将类类型声明为 ATL。选择“custom interface”并输入名称 CEffects。请注意,您的接口名称现在将是 IEffects
  3. 通过右键单击 IEffects 接口并选择“Add Method”来添加一个方法,并将其命名为 GetName。将参数指定如下:[out, retval] BSTR *retval.
  4. 构建您的 ATL Server 并注意已创建一个类型库 Effects.tlb。稍后我们将将其导入到我们的插件中。

创建插件

现在让我们创建一个 Effect 插件。

  1. 创建另一个 ATL Server 并将其命名为 EffectMosaic
  2. 添加一个 ATL 对象,并使用短名称 Effect。请注意,接口名称是 IEffect
  3. 右键单击类并选择“Implement Interface”。
  4. 从对话框中,浏览到 Effects ATL Server 项目目录中的 Effects.tlb 类型库并选择它。
  5. 选择 IEffects 复选框,然后单击 OK。

此时,您已使用导入的类型库继承了 IEffects 接口,如果您查看 Effect.h 头文件,您会发现以下代码:

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

这是向导生成的 IEffectGetName() 方法的实现。您可以更改它以返回有用的内容。以下代码将效果的名称返回给组件的用户。在我们的示例中,它允许我们的主应用程序在列表框中显示组件的名称。

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

使用自定义组件类别

我们必须对组件进行分组,以便在运行时被我们的应用程序找到。为此,我们将指定一个 COM 类别,我们的效果组件属于该类别。COM 类别由 GUID(难道不是一切都一样吗?)唯一标识,称为类别 ID 或 CATID。可以想象,有许多预定义的类别,但我们将定义一个自定义类别。顺便说一句,您可以在注册表中查看类别信息,位于以下键下:

HKEY_CLASSES_ROOT/Component Categories
  1. 首先,我们需要为我们的自定义类别定义一个 GUID,我们使用 GUID 生成工具 guidgen.exe(应由 Visual Studio 提供)来完成此操作。
  2. 将新 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 } };

现在我们只需在我们的每个效果组件中建立自定义类别映射。

  1. 在 Mosaic 组件的 Effect.h 头文件中,在对象映射部分下添加以下代码,并记住包含 EffectsCategory.h 头文件!
    BEGIN_CATEGORY_MAP(CEffect)
       IMPLEMENTED_CATEGORY(CATID_EFFECTS)
    END_CATEGORY_MAP()
    
  2. 重新构建您的组件,然后在注册表中查看它是否存在。

修改应用程序

接下来,我们必须修改我们的主应用程序以使用 Category Manager 在运行时获取我们新分类的组件。EffectBrowerDlg.cpp 文件必须包含以下文件:

  1. 包含类别 GUID 的 EffectCategories.h 头文件。
  2. 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() 方法。

  1. 从 ClassWizard 中,添加一个函数来处理对 IDC_EFFECTS 列表框的双击。
  2. 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 接口调用每个组件的方法。

致谢和参考

  1. geo_m - CodeProject 会员宝贵的帮助,帮助我理解 COM 继承的语法。
  2. Len Holgate 在 CodeProject 上的“编写可扩展的应用程序”一文。
  3. Andrew W. Troelsen 的《Developer's Workshop to COM & ATL 3.0》——一本很棒的 COM 入门书,强烈推荐!
© . All rights reserved.