分享
 
 
 

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

王朝other·作者佚名  2006-03-30
窄屏简体版  字體: |||超大  

编译/hangwire

下载范例源代码

问题:

我要编写一个支持ActiveX文档插件(Plug-ins)的应用程序。为了创建一个已安装插件的菜单,在程序启动时我扫描注册表查找已安装的ActiveX组件。对于每一个ActiveX组件创建一个实例并查询一个叫IMyAppPlugin的专门接口。如果这个接口存在,那么我就认为这个组件就是我的程序所要的插件。这样做好像行不通,尤其是安装有多个ActiveX组件时做起来就更困难。有没有更好的办法处理这种问题?

解答:

对于这种情况,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

////////////////////////////////////////////////////////////////

// ICatRegister interface, edited from comcat.h

//

class ICatRegister : public IUnknown {

public:

virtual HRESULT RegisterCategories(

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 HRESULT UnRegisterClassImplCategories(

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 HRESULT UnRegisterClassReqCategories(

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 有关的代码:(全部源代码可以从本文最前面的链接下载)

// CoolCat.h — helper stuff for COMponent categories.

//

#pragma once

#include

//////////////////

// Helper function to get GUID in human-readable format as CString.

//

inline CString CStringFromGuid(GUID& guid)

{

LPOLESTR pstr=NULL;

StringFromCLSID(guid, &pstr);

return CString(pstr);

}

////////////////

// Handy Category Information class. Instantiate and go.

//

class CCatInformation : public CComPtr {

public:

CCatInformation() {

CoCreateInstance(CLSID_StdComponentCategoriesMgr, NULL,

CLSCTX_INPROC);

ASSERT(p);

}

};

//////////////////

// Handy class to enumerate categories

//

class CCatIterator {

protected:

CComPtr spEnumCatInfo; // IEnumCATEGORYINFO

CCatInformation spCatInfo; // ICatInformation

public:

CCatIterator(LCID lcid = GetUserDefaultLCID()) {

HRESULT hr = spCatInfo->EnumCategories(lcid, &spEnumCatInfo);

ASSERT(SUCCEEDED(hr));

}

BOOL Next(CATEGORYINFO& catinfo) {

ULONG nRet=0;

return SUCCEEDED(spEnumCatInfo->Next(1, &catinfo, &nRet)) &&

nRet==1;

}

};

//////////////////

// Handy class to enumerate classes that implement a category

//

class CCatClassIterator {

protected:

CComPtr spEnumCLSID; // IEnumCLSID

CCatInformation spCatInfo; // ICatInformation

public:

CCatClassIterator(CATID* arImplCatids, ULONG nImpl,

CATID* arReqdCatids=NULL, ULONG nReqd=0) {

HRESULT hr = spCatInfo->EnumClassesOfCategories(

nImpl, // num implemented cats in array

arImplCatids, // array of cats to look for (implement)

nReqd, // num required categories in array

arReqdCatids, // array of required categories to look for

&spEnumCLSID); // IEnum returned

ASSERT(SUCCEEDED(hr));

}

BOOL Next(CLSID& clsid) {

ULONG nRet=0;

return SUCCEEDED(spEnumCLSID->Next(1, &clsid, &nRet)) && nRet==1;

}

};

View.h

#pragma once

//////////////////

// Right pane is a list of controls that implement a category.

//

class CRightView : public CListView {

public:

CRightView();

virtual ~CRightView();

BOOL ShowCategory(CATID& catid);

protected:

virtual void OnInitialUpdate(); // called first time after construct

virtual void OnDraw(CDC* pDC); // overridden to draw this view

virtual BOOL PreCreateWindow(CREATESTRUCT& cs);

DECLARE_DYNCREATE(CRightView)

DECLARE_MESSAGE_MAP()

};

//////////////////

// Left pane is a list of categories.

//

class CLeftView : public CListView {

public:

virtual ~CLeftView();

virtual void OnDraw(CDC* pDC); // overridden to draw this view

virtual BOOL PreCreateWindow(CREATESTRUCT& cs);

void SetRightPane(CRightView* pRightPane) {

m_pRightPane = pRightPane;

}

protected:

CRightView* m_pRightPane;

CLeftView();

void PopulateCategoryList();

virtual void OnInitialUpdate(); // called first time after construct

afx_msg void OnItemChanged(NMHDR* pNMHDR, LRESULT* pRes);

afx_msg LRESULT OnWinMgr(WPARAM wp, LPARAM lp);

DECLARE_MESSAGE_MAP()

DECLARE_DYNCREATE(CLeftView)

};

LeftView.cpp

//

#include "stdafx.h"

#include "View.h"

#include "WinMgr.h"

#include "CoolCat.h"

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

IMPLEMENT_DYNCREATE(CLeftView, CListView)

BEGIN_MESSAGE_MAP(CLeftView, CListView)

ON_NOTIFY_REFLECT(LVN_ITEMCHANGED,OnItemChanged)

ON_REGISTERED_MESSAGE(WM_WINMGR, OnWinMgr)

END_MESSAGE_MAP()

CLeftView::CLeftView() : m_pRightPane(NULL)

{

}

CLeftView::~CLeftView()

{

}

BOOL CLeftView::PreCreateWindow(CREATESTRUCT& cs)

{

cs.style |= LVS_REPORT | LVS_SORTASCENDING | LVS_NOSORTHEADER;

return CListView::PreCreateWindow(cs);

}

void CLeftView::OnDraw(CDC* pDC)

{

}

//////////////////

// First-time init: add column headers

//

void CLeftView::OnInitialUpdate()

{

CListView::OnInitialUpdate();

const COLWIDTH = 250;

CListCtrl& lc = GetListCtrl();

lc.InsertColumn(0, _T("Category Name"),LVCFMT_LEFT,COLWIDTH);

lc.InsertColumn(1, _T("CATID"),LVCFMT_LEFT,COLWIDTH,1);

PopulateCategoryList();

}

//////////////////

// Populate list of categories.

//

void CLeftView::PopulateCategoryList()

{

CListCtrl& lc = GetListCtrl();

lc.DeleteAllItems();

CATEGORYINFO catinfo;

CCatIterator it;

while (it.Next(catinfo)) {

// add category name to list

CString sName = catinfo.szDescription;

if (sName.IsEmpty()) {

sName = _T("");

}

int iItem = lc.InsertItem(0,sName);

// Add CATID as 1st subitem

lc.SetItemText(iItem,1,CStringFromGuid(catinfo.catid));

}

}

//////////////////

// User selected a new category: show controls in right pane.

//

void CLeftView::OnItemChanged(NMHDR* pNMHDR, LRESULT* pRes)

{

NMLISTVIEW nm = *(NMLISTVIEW*)pNMHDR;

if (nm.iItem>=0 && (nm.uNewState & LVIS_SELECTED)) {

CListCtrl& lc = GetListCtrl();

CString sguid = lc.GetItemText(nm.iItem,1);

CATID catid;

USES_CONVERSION;

if (SUCCEEDED(CLSIDFromString(T2OLE((LPCTSTR)sguid),&catid)))

m_pRightPane->ShowCategory(catid);

else

MessageBeep(0);

}

*pRes= 0;

}

//////////////////

// Handle WinMgr request for size info: compute TOFIT size for list view,

// which is sum of widths of columns.

//

LRESULT CLeftView::OnWinMgr(WPARAM wp, LPARAM lp)

{

ASSERT(lp);

NMWINMGR& nmw = *(NMWINMGR*)lp;

if (nmw.code==NMWINMGR::GET_SIZEINFO && (int)wp==GetDlgCtrlID()) {

CSize sz(0,0);

CListCtrl& lc = GetListCtrl();

int nCols = lc.GetHeaderCtrl()->GetItemCount();

for (int iCol=0; iCol");

lc.SetItemText(iItem,1,sProgID);

}

return TRUE;

}

//

CatView是个典型的将窗口切分成两个窗格的程序,左边窗格是种类清单,当单击其中一条记录,右边窗格会显示相应的实现这个种类的类信息。(CatView程序中使用了一个类CwinMgr,这个类将在另外一篇文章中做专门讨论:“创建一个随心所欲定制窗口尺寸的类”)。图一所示,选中“Active Scripting Engine with Parsing”列表项,则右边的窗格将显示实现它的各个组件:XML,Java,Visual Basic和PerlScript脚本引擎。CatView中的两个主要的函数是CLeftView::PopulateCategoryList 和 CRightView::ShowCategory。为了简单起见,我实现了一些有用的辅助类(在头文件CoolCat.h中)。第一各类是CCatInformation,它用ATL智能指针封装了ICatInformation接口。

//

class CCatInformation : public CComPtr {

public:

CCatInformation() {

CoCreateInstance(CLSID_StdComponentCategoriesMgr,

NULL, CLSCTX_INPROC);

}

};

有了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键是必不可少的。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有