荔园在线

荔园之美,在春之萌芽,在夏之绽放,在秋之收获,在冬之沉淀

[回到开始] [上一篇][下一篇]


发信人: DuMiYue (TH@Figo), 信区: Visual
标  题: 理解COM编程中的“种类”(Category)概念
发信站: 荔园晨风BBS站 (Tue Mar 19 15:03:16 2002), 转信

理解COM编程中的“种类”(Category)概念
编译/hangwire
下载范例源代码
问题:
   我要编写一个支持ActiveX文档插件(Plug-ins)的应用程序。为了创建一个已安装
插件的菜单,在程序启动时我扫描注册表查找已安装的ActiveX组件。对于每一个Activ
eX组件创建一个实例并查询一个叫IMyAppPlugin的专门接口。如果这个接口存在,那么
我就认为这个组件就是我的程序所要的插件。这样做好像行不通,尤其是安装有多个Ac
tiveX组件时做起来就更困难。有没有更好的办法处理这种问题?
解答:
   对于这种情况,Windows确实有更好的办法来解决:既种类(category)。对于开发
人员来说,种类是一种ActiveX控件。名字可以随意取,如“My Acme Plugin”或者“B
lue Insertable Thingies”。对于COM而言,种类只是一个GUID——不同的是种类用CA
TID表示GUID,这有点像表示某个类的GUID叫做CLSID一样。
   那么在实际编程中如何使用CATID呢?首先要生成一个新的GUID(使用GUIDGEN或其它
的同类程序),我们且把这个新生成的GUID叫做CATID_AcmePlugin。然后,用一个专门
的COM接口ICatRegister来注册你的种类。完成这个工作的地方一般是在DllRegisterSe
rver函数中。为了获得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将有关哪个类实现哪个种类的信息放入注册表,以便Wi
ndows能快速读到它,而不用像你最开始所做的那样去实例化每一个组件来查找IMyAcme
Plugin接口。
与种类的注册类似,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 unre
g
      CATID rgcatid[]);             // CATIDs to unregister
};
//
   对于注册种类编程的实例请参见VC知识库的另外一篇文章:“编写可复用性更强的M
FC代码”。
讲了那么多有关注册的问题。现在假设你写了一个容器并且你想要产生一个插件(Acme
Plugins)清单——既实现CATID_AcmePlugin的组件。Windoews提供了另一个接口,ICat
Information:
//
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智能指针也是如此,我把这几招C
OM编程技术都封装在一个辅助类CCatIterator中,以便使用起来方便一些。有了CcatIt
erator辅助类,要做的事情很简单:
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::EnumClassesOf
Categories以及枚举器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的其余部分,它与Window
s及其MFC有关。CatView是一个文档/视结构的应用,但CDummyDoc只是为MFC而存在的。
CMainFrame::OnCreateClient创建由窗格并在执行了通常的CframeWnd之后与左边窗格关
联起来。在程序中唯有CLeftView::OnWinMgr是比较特殊的东西,它通过添加列宽来报告
列表视图画面的TOFIT尺寸。(有关WinMgr和TOFIT的内容,请参见另外一篇文章:“创
建一个随心所欲定制窗口尺寸的类”)。
   本文附带的CatView例子可以从文章开始处的链接下载。编译后可以在自己的机器上
运行,以观察机器上注册的种类。你会注意到一些晦涩难董的种类(如Visual InterDe
v Web Site Wizards)以及一些通用的控件,自动化对象和可插入种类。从COM的历史看
,可插入种类是整个种类概念的祖先。回溯到早期,Visual Basic需要某种方式来获得
哪个对象能被插入表单(forms),不用实例化每一个在注册表中的类来查找(QueryIn
terface)IOleInPlaceObject接口。解决方法是添加一个专门的键值,HKCR\CLSID\{CL
SID}\Insertable,它告诉Visual Basic 类是可插入的(insertable)。后来微软扩展
了这个机制变成更一般的概念,它就是我们在这里所说的种类。今天,Insertable键是
个遗留下来的东西,对于要在16位应用插入32位对象,Insertable键是必不可少的。

--
他能够不知不觉令到我倾心
    也能够一句说话而留下痛恨
        感情最光辉一刹热暖我一生
            爱人远走了以后仍然是爱人

※ 来源:·荔园晨风BBS站 bbs.szu.edu.cn·[FROM: 218.30.30.9]


[回到开始] [上一篇][下一篇]

荔园在线首页 友情链接:深圳大学 深大招生 荔园晨风BBS S-Term软件 网络书店