理解COM编程中的“种类”(Category)概念

COM Category
转载自www.vckbase.com,有改动
Q
我要编写一个支持 ActiveX 文档插件( Plug-ins )的应用程序。为了创建一个已安装插件的菜单,在程序启动时我扫描注册表查找已安装的 ActiveX 组件。对于每一个 ActiveX 组件创建一个实例并查询一个叫 IMyAppPlugin 的专门接口。如果这个接口存在,那么我就认为这个组件就是我的程序所要的插件。这样做好像行不通,尤其是安装有多个 ActiveX 组件时做起来就更困难。有没有更好的办法处理这种问题?
A
对于这种情况, Windows 确实有更好的办法来解决:既种类( category )。对于开发人员来说,种类是一种 ActiveX 控件。名字可以随意取,如 “My Acme Plugin” 或者 “Blue Insertable Thingies” 。对于 COM 而言,种类只是一个 GUID—— 不同的是种类用 CATID 表示 GUID ,这有点像表示某个类的 GUID 叫做 CLSID 一样。
那么在实际编程中如何使用 CATID 呢?首先要生成一个新的 GUID (使用 GUIDGEN 或其它的同类程序),我们且把这个新生成的 GUID 叫做 CATID_AcmePlugin 。然后,用一个专门的 COM 接口 ICatRegister 来注册你的种类。完成这个工作的地方一般是在 DllRegisterServer 函数中。为了获得 ICatRegister 接口,必须调用 CoCreateInstance 或实现同样功能的函数。
// DllRegisterServer
CComPtr spcr;
spcr.CoCreateInstance(CLSID_StdComponentCategoriesMgr,NULL, CLSCTX_INPROC);
这段代码使用 ATL 智能指针; CComPtr::CoCreateInstance 还能用 ICatRegister IID 调用 ::CoCreateInstance 。一旦有了 ICatRegister ,便可以调用 RegisterCategories 。方法是先用自己的种类信息填写 CATEGORYINFO 结构。
CATEGORYINFO catinfo;
catinfo.catid = CATID_AcmePlugin;
catinfo.lcid = 0x0409;  // locale=english
USES_CONVERSION;  // uses A2W
wcscpy(catinfo.szDescription, A2W("My Acme Plugin."));
pcr->RegisterCategories(1, &catinfo);
接下来的任务是如何告诉 COM 你的 COM 类是 Acme Plugin ICatRegister 也有相应的方法来做这件事情,它就是 RegisterClassImplCategories
// 也是在 DllRegisterServer
CATID catid = CATID_AcmePlugin;
pcr->RegisterClassImplCategories(CLSID_MyPluginObj, 1, &catid);
这样就注册了你的 COM 类,实现种类 CATID_AcmePlugin 。是不是很简单啊!这些都是此类编程的套路。 ICatRegister 将有关哪个类实现哪个种类的信息放入注册表,以便 Windows 能快速读到它,而不用像你最开始所做的那样去实例化每一个组件来查找 IMyAcmePlugin 接口。
与种类的注册类似, ICatRegister 也有用注销种类的方法,这两个方法对于种类而言都是必须的(相对于实现而言),也就是说,你的 COM 类需要其容器来实现那些种类。当你的组件需要专门的回调接口时,就必须实现种类。下面是完整的 ICatRrgister 接口:
// ICatRegister interface, edited from comcat.h
 
class ICatRegister : public IUnknown {
public:
   virtual HRESULTRegisterCategories(
      ULONG cCategories,             // number of categories to register
      CATEGORYINFO rgCategoryInfo[]);// info for each one
       
   virtual HRESULT UnRegisterCategories(
      ULONG cCategories,           // number of categories to unregister
      CATID rgcatid[]);               // their CATIDs
 
   virtual HRESULT RegisterClassImplCategories(
      REFCLSID rclsid,              // COM class ID
      ULONG cCategories,           // number of categories it implements
      CATID rgcatid[]);               // their CATIDs
 
   virtual HRESULTUnRegisterClassImplCategories(
      REFCLSID rclsid,              // COM class ID
      ULONG cCategories,          // num implemented categories to unreg
      CATID rgcatid[]);               // their CATIDs
 
   virtual HRESULT RegisterClassReqCategories(
      REFCLSID rclsid,             // COM class ID
      ULONG cCategories,           // number of categories it requires
      CATID rgcatid[]);               // required CATIDs
 
   virtual HRESULTUnRegisterClassReqCategories(
      REFCLSID rclsid,              // COM class ID
      ULONG cCategories,           // number of req''''d categories to unreg
      CATID rgcatid[]);               // CATIDs to unregister
};
 
对于注册种类编程的实例请参见 VC 知识库的另外一篇文章: 编写可复用性更强的 MFC 代码
讲了那么多有关注册的问题。现在假设你写了一个容器并且你想要产生一个插件 (Acme Plugins) 清单 —— 既实现 CATID_AcmePlugin 的组件。 Windoews 提供了另一个接口, ICatInformation
//
ICatInformation
class ICatInformation : public IUnknown {
public:
   // Enumerate all categories
   virtual HRESULT EnumCategories(
      LCID lcid,
      IEnumCATEGORYINFO ** ppenumCategoryInfo);
 
   // Get locale-specific category descriptor
   virtual HRESULT GetCategoryDesc(
      REFCATID rcatid,
      LCID lcid,
      LPWSTR *pszDesc);
 
   // Enumerate classes that implement/require given categories
  virtual HRESULT EnumClassesOfCategories(
      ULONG cImplemented,
      CATID rgcatidImpl[],
      ULONG cRequired,
      CATID rgcatidReq[],
      IEnumGUID **ppenumClsid);
 
   // Determine if class implements/requires given categories
   virtual HRESULT IsClassOfCategories(
      REFCLSID rclsid,
      ULONG cImplemented,
      CATID rgcatidImpl[ ],
      ULONG cRequired,
      CATID rgcatidReq[ ]);
 
   // Enumerate categories implemented by given class
   virtual HRESULT EnumImplCategoriesOfClass(
      REFCLSID rclsid,
      IEnumGUID **ppenumCatid);
 
   // Enumerate categories required by given class
   virtual HRESULT EnumReqCategoriesOfClass(
      REFCLSID rclsid,
      IEnumGUID **ppenumCatid);
};
 
用这接口可以枚举实现给定种类的类。为了说明 ICatInformation 接口使用,我写了一个小程序 CatView ,用这个程序可以浏览系统中注册的种类。如图一所示:
图一 CatView 浏览系统中注册的种类
 
点击此处 可以下载 CatView 有关的代码,此处不再粘贴源码。
CatView 是个典型的将窗口切分成两个窗格的程序,左边窗格是种类清单,当单击其中一条记录,右边窗格会显示相应的实现这个种类的类信息。( CatView 程序中使用了一个类 CwinMgr ,这个类将在另外一篇文章中做专门讨论: 创建一个随心所欲定制窗口尺寸的类 )。图一所示,选中 “Active Scripting Engine with Parsing” 列表项,则右边的窗格将显示实现它的各个组件: XML Java Visual Basic PerlScript 脚本引擎。 CatView 中的两个主要的函数是 CLeftView::PopulateCategoryList CRightView::ShowCategory 。为了简单起见,我实现了一些有用的辅助类(在头文件 CoolCat.h 中)。第一各类是 CCatInformation ,它用 ATL 智能指针封装了 ICatInformation 接口。
// Handy Category Information class. Instantiate and go.
class CCatInformation :publicCComPtr<ICatInformation> {
public:
         CCatInformation() {
                   CoCreateInstance (CLSID_StdComponentCategoriesMgr, NULL, CLSCTX_INPROC);
                   ASSERT(p);
         }
};
有了 CCatInformation 类,就不用再调用 CoCreateInstance—— 实例化,然后直接使用类对象。
CCatInformation spCatInfo;
spCatInfo->SomeMethod(...);
为了枚举系统中的组件种类,调用 ICatInformation::EnumCategories 。这个函数回传一个 IEnumCATEGORYINFO 接口指针,然后用这个指针枚举种类。
// IEnumCATEGORYINFO
CCatInformation spCatInfo;
CComPtr spEnumCatInfo;
HRESULT hr = spCatInfo->EnumCategories(GetUserDefaultLCID(),&spEnumCatInfo);
ASSERT(SUCCEEDED(hr));
 
// 使用指针枚举种类
ULONG nRet=0;
CATEGORYINFO catinfo;
   while (SUCCEEDED(spEnumCatInfo->Next(1, &catinfo, &nRet)) && nRet==1) {
   // add catinfo to list
}
 
COM 的技术机制实际上就这么几招。即使是使用 ATL 智能指针也是如此,我把这几招 COM 编程技术都封装在一个辅助类 CCatIterator 中,以便使用起来方便一些。有了 CcatIterator 辅助类,要做的事情很简单:
CATEGORYINFO catinfo;
CCatIterator it;
while (it.Next(catinfo)) {
 // add catinfo to list
}
CLeftView::PopulateCategoryList CCatIterator 类以名字和每个种类的 CATID 构造列表视图。每次调用 Next 来将下一个种类的信息填入 catinfo 。在这里请记住我的一些经验之谈,在进行 COM 编程时,做好是编写一些自己的小型辅助类以免去处理那些头疼的 HRESULTs 和接口指针,尖括弧以及 Release 操作。我是个唯美主义者,要求自己的代码不仅要正确运行,还要求好看。
一旦具备了 CATID ,就可以用 ICatInformation 来得到实现种类的 COM 类清单。例如,实现 CATID_AcmePlugin 的所有控件。其中最关键的部分是 ICatInformation::EnumClassesOfCategories 以及枚举器 IEnumCLSID 。同样我也写了一个类来封装这些东西。
CLSID clsid;
CCatClassIterator it(&catid, 1);
while (it.Next(clsid)) {
 // add clsid to list
}
ICatInformation::EnumClassesOfCategories 类似, CCatClassIterator 可以使你指定多个实现的种类。如 查找所有 AcmePlugin Blue Insertable Thingies 控件 。在这种情况下,要传递一个包含两个 CATIDs 的数组。你还能指定一个或多个必须的种类来查找需要一个或多个给定的控件。通过缺省值 NULL CCatClassIterator 隐藏了所有额外的参数。
以上内容讨论了 COM 技术中对种类的编程。下面将谈谈 CatView 的其余部分,它与 Windows 及其 MFC 有关。 CatView 是一个文档 / 视结构的应用,但 CDummyDoc 只是为 MFC 而存在的。 CMainFrame::OnCreateClient 创建由窗格并在执行了通常的 CframeWnd 之后与左边窗格关联起来。在程序中唯有 CLeftView::OnWinMgr 是比较特殊的东西,它通过添加列宽来报告列表视图画面的 TOFIT 尺寸。(有关 WinMgr TOFIT 的内容,请参见另外一篇文章: 创建一个随心所欲定制窗口尺寸的类 )。
         本文附带的 CatView 例子可以从文章开始处的链接下载。编译后可以在自己的机器上运行,以观察机器上注册的种类。你会注意到一些晦涩难董的种类(如 Visual InterDev Web Site Wizards )以及一些通用的控件,自动化对象和可插入种类。从 COM 的历史看,可插入种类是整个种类概念的祖先。回溯到早期, Visual Basic 需要某种方式来获得哪个对象能被插入表单( forms ),不用实例化每一个在注册表中的类来查找( QueryInterface IOleInPlaceObject 接口。解决方法是添加一个专门的键值, HKCR/CLSID/{CLSID}/Insertable ,它告诉 Visual Basic 类是可插入的( insertable )。后来微软扩展了这个机制变成更一般的概念,它就是我们在这里所说的种类。今天, Insertable 键是个遗留下来的东西,对于要在 16 位应用插入 32 位对象, Insertable 键是必不可少的。
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值