首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> 關於VC++

COM 元件分類機制:CATID 與 ICatRegister 完整應用指南

     編輯:關於VC++

問題背景

開發支援 ActiveX 文件外掛的應用程式時,傳統做法是掃描註冊表、逐一建立元件實例並查詢特定介面 (如 IMyAppPlugin)。這不僅效率低落,在大量元件安裝時更會導致啟動緩慢與資源浪費。Windows 提供了更優雅的解決方案:元件分類。

核心概念:什麼是分類?

  • 開發者視角:分類是一個邏輯群組名稱 (如 "My Acme Plugin")
  • COM 視角:分類本質是一個 GUID,慣稱 CATID (類比 CLSID 標識類別)
  • 優勢:系統在註冊表預先建立索引,容器可直接列舉實現特定分類的類別,無需實例化

分類註冊流程

1. 產生 CATID

// 使用 GUIDGEN 產生
// {XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}
#define CATID_AcmePlugin { ... }

2. 註冊分類資訊


// DllRegisterServer 中
CComPtr<ICatRegister> pcr;
pcr.CoCreateInstance(CLSID_StdComponentCategoriesMgr, NULL, CLSCTX_INPROC);

CATEGORYINFO catinfo = {0};
catinfo.catid = CATID_AcmePlugin;
catinfo.lcid = 0x0409; // 英文
wcscpy(catinfo.szDescription, L"My Acme Plugin");

pcr->RegisterCategories(1, &catinfo);

3. 關聯類別與分類

CATID catid = CATID_AcmePlugin;
pcr->RegisterClassImplCategories(CLSID_MyPluginObj, 1, &catid);

關鍵介面:ICatRegister


class ICatRegister : public IUnknown {
public:
    virtual HRESULT RegisterCategories(ULONG cCategories, CATEGORYINFO rgCategoryInfo[]);
    virtual HRESULT UnRegisterCategories(ULONG cCategories, CATID rgcatid[]);
    virtual HRESULT RegisterClassImplCategories(REFCLSID rclsid, ULONG cCategories, CATID rgcatid[]);
    virtual HRESULT UnRegisterClassImplCategories(REFCLSID rclsid, ULONG cCategories, CATID rgcatid[]);
    virtual HRESULT RegisterClassReqCategories(REFCLSID rclsid, ULONG cCategories, CATID rgcatid[]);
    virtual HRESULT UnRegisterClassReqCategories(REFCLSID rclsid, ULONG cCategories, CATID rgcatid[]);
};
  • ImplCategories:類別實現的分類 (對外提供功能)
  • ReqCategories:類別要求容器實現的分類 (依賴特定容器功能)

列舉分類與元件:ICatInformation


class ICatInformation : public IUnknown {
public:
    virtual HRESULT EnumCategories(LCID lcid, IEnumCATEGORYINFO** ppenum);
    virtual HRESULT GetCategoryDesc(REFCATID rcatid, LCID lcid, LPWSTR* pszDesc);
    virtual HRESULT EnumClassesOfCategories(
        ULONG cImplemented, CATID rgcatidImpl[],
        ULONG cRequired, CATID rgcatidReq[],
        IEnumGUID** ppenumClsid);
    virtual HRESULT IsClassOfCategories(REFCLSID rclsid, ULONG cImpl, CATID rgcatidImpl[], ULONG cReq, CATID rgcatidReq[]);
    virtual HRESULT EnumImplCategoriesOfClass(REFCLSID rclsid, IEnumGUID** ppenumCatid);
    virtual HRESULT EnumReqCategoriesOfClass(REFCLSID rclsid, IEnumGUID** ppenumCatid);
};

實用輔助類別 (ATL 智慧指標封裝)


// CoolCat.h
#pragma once
#include <comcat.h>

inline CString CStringFromGuid(GUID& guid) {
    LPOLESTR pstr = NULL;
    StringFromCLSID(guid, &pstr);
    return CString(pstr);
}

class CCatInformation : public CComPtr<ICatInformation> {
public:
    CCatInformation() {
        CoCreateInstance(CLSID_StdComponentCategoriesMgr, NULL, CLSCTX_INPROC);
        ATLASSERT(p != NULL);
    }
};

class CCatIterator {
    CComPtr<IEnumCATEGORYINFO> m_spEnum;
    CCatInformation m_spCatInfo;
public:
    CCatIterator(LCID lcid = GetUserDefaultLCID()) {
        m_spCatInfo->EnumCategories(lcid, &m_spEnum);
    }
    BOOL Next(CATEGORYINFO& info) {
        ULONG fetched = 0;
        return SUCCEEDED(m_spEnum->Next(1, &info, &fetched)) && fetched == 1;
    }
};

class CCatClassIterator {
    CComPtr<IEnumGUID> m_spEnum;
    CCatInformation m_spCatInfo;
public:
    CCatClassIterator(CATID* impl, ULONG nImpl, CATID* req = NULL, ULONG nReq = 0) {
        m_spCatInfo->EnumClassesOfCategories(nImpl, impl, nReq, req, &m_spEnum);
    }
    BOOL Next(CLSID& clsid) {
        ULONG fetched = 0;
        return SUCCEEDED(m_spEnum->Next(1, &clsid, &fetched)) && fetched == 1;
    }
};

CatView 範例應用架構

雙窗格 MFC 應用程式:

  • 左窗格:CLeftView 列舉所有系統分類 (呼叫 CCatIterator)
  • 右窗格:CRightView 顯示實現所選分類的類別清單 (呼叫 CCatClassIterator)

左窗格填充邏輯


void CLeftView::PopulateCategoryList() {
    CListCtrl& lc = GetListCtrl();
    lc.DeleteAllItems();
    CATEGORYINFO info;
    CCatIterator it;
    while (it.Next(info)) {
        CString name = info.szDescription;
        if (name.IsEmpty()) name = _T("(無描述)");
        int idx = lc.InsertItem(0, name);
        lc.SetItemText(idx, 1, CStringFromGuid(info.catid));
    }
}

右窗格顯示實現類別


BOOL CRightView::ShowCategory(CATID& catid) {
    CListCtrl& lc = GetListCtrl();
    lc.DeleteAllItems();

    CCatClassIterator it(&catid, 1);
    CLSID clsid;
    while (it.Next(clsid)) {
        // 解析 ProgID、描述等註冊表資訊
        int idx = lc.InsertItem(0, clsidStr);
        lc.SetItemText(idx, 1, progId);
    }
    return TRUE;
}

歷史淵源:從 Insertable 到 Categories

早期 Visual Basic 需識別可插入表單的物件,採用 HKCR\CLSID\{CLSID}\Insertable 註冊表鍵值。微軟後將此機制泛化為通用的分類系統。現今 Insertable 鍵值仍保留以支援 16/32 位元互通,但新開發應全面採用 CATID 機制。

總結

元件分類機制解決了「如何高效發現實現特定契約的 COM 元件」的核心問題。透過 ICatRegister 註冊與 ICatInformation 列舉,應用程式可在啟動時快速建立外掛選單,避免大量 CoCreateInstance + QueryInterface 的效能惡夢。建議所有 COM 外掛架構採用此標準機制。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved