可插拔组件(第一部分)






4.96/5 (16投票s)
2003年6月17日
4分钟阅读

70323

1118
一篇关于使用组件类别创建可插拔组件的文章。
 
 
 
  
 
链接
引言
最近,我需要编写具有相似接口的组件,并一次将其中一个插入我的应用程序。虽然有很多方法可以做到这一点,但我希望使用一种标准的方法,这种方法相当直接且易于编写。由于其基本概念满足了这两项需求,并且在行业中广为接受,因此我选择了 COM。我开始翻阅我所有的书籍,并在互联网上浏览,但我找不到我想要的指南。感谢 Len Holgate。他的两篇文章(上面的链接)帮助我开始了。
这是本系列的第一部分。在本部分中,我将演示一个非常简单的接口,该接口在两个不同的 COM 对象中实现方式不同。
背景
我没有足够的时间或空间来全面介绍 COM 和/或 ATL,因此我将假设您至少熟悉这些概念。本文将重点介绍什么是可重用接口,什么是组件类别,以及如何在您的应用程序中使用它们。
什么是可重用接口?嗯,它的理念是它是一个接口,被几个对象使用,而这些对象可能以不同的方式实现它。
ComCatDraw.idl
// IComCatDraw - Category Interface Definition
[
    object,
    uuid(C49A2274-8D1F-47b9-8476-8250174956EB),
    helpstring("IComCatDraw Interface"),
    pointer_default(unique)
]
interface IComCatDraw : IUnknown
{
    import "unknwn.idl" ;
    HRESULT Draw([in] HDC hDC);
};
这个特定的示例直接派生自 IUnknown; 然而,您可以从任何间接派生自 IUnknown 的对象派生您的接口(例如 IDispatch)。这是一个非常简单的接口,只有一个方法(Draw)。我们将在第二部分中讨论更复杂的接口。此接口将成为我们组件的基本接口。
现在,我们有了一个通用的接口。这很好,但现在我们该如何使用它呢?嗯,首先我们需要创建一个简单的 COM 对象,并使其接口派生自我们的通用接口。我使用 ALT COM AppWizard 来完成此操作(虽然您也可以手动完成)。这是蓝色组件的派生接口
ComCatBlue.idl
// 
ComCatBlue.idl : IDL source for ComCatBlue.dll
//
// This file will be processed by the MIDL tool to
// produce the type library (ComCatBlue.tlb) and marshalling code.
import "oaidl.idl";
import "ocidl.idl";
import "ComCatDraw.idl";
    [
        object,
        uuid(98639EC3-6C69-490B-BDC2-095ECC133F30),
        helpstring("IComCatDrawBlue Interface"),
        pointer_default(unique)
    ]
    interface IComCatDrawBlue : IComCatDraw
    {
    };
[
    uuid(669E1E78-D56A-4136-A926-2EC613C10720),
    version(1.0),
    helpstring("ComCatBlue 1.0 Type Library")
]
library COMCATBLUELib
{
    importlib("stdole32.tlb");
    importlib("stdole2.tlb");
    [
        uuid(653BF0E4-3D19-4000-A189-AFA128B7E99B),
        helpstring("ComCatDrawBlue Class")
    ]
    coclass ComCatDrawBlue
    {
        [default] interface IComCatDrawBlue;
    };
};
注意这些变化。另外,我们不需要在 IComCatDrawBlue 接口中声明 Draw 方法,因为它已经在基类中声明了。此时我们无法编译项目,因为我们还没有实现此函数(如果您尝试,您将收到链接器错误)。要解决此问题,您需要在实现类中添加以下内容
ComCatDrawBlue.h
class ATL_NO_VTABLE CComCatDrawBlue { . . . // IComCatDrawBlue public: STDMETHOD(Draw)(HDC hDC); };
现在我们可以实现这个函数了。
ComCatDrawBlue.cpp
STDMETHODIMP 
CComCatDrawBlue::Draw(HDC hDC)
{
    AFX_MANAGE_STATE(AfxGetStaticModuleState())
    CDC* pDC = CDC::FromHandle(hDC);
    CBrush brush;
    brush.CreateSolidBrush(RGB(0x00, 0x00, 0xFF));
    CBrush* pOldBrush = pDC->SelectObject(&brush);
    pDC->RoundRect(CRect(10, 10, 210, 210), CPoint(50, 50));
    pDC->SelectObject(pOldBrush);
    return S_OK;
}
我们现在可以成功编译此项目;但是,它对我们没有什么好处。我们只能直接绑定到对象,而这正是我们试图摆脱的。要做到这一点,我们需要使用 GUIDGEN 为我们定义一个新的 GUID。
ComCatDrawCategory.h
// {C49A2274-8D1F-47b9-8476-8250174956EB} DEFINE_GUID(CATID_ComCatDrawCategory, 0xc49a2274, 0x8d1f, 0x47b9, 0x84, 0x76, 0x82, 0x50, 0x17, 0x49, 0x56, 0xeb);
这本身对我们没有任何用处。但是,如果我们创建实现类头文件中的类别映射,该对象将被注册为实现了指定的类别。这并不一定意味着什么,如果使用不当。类别注册表键(“必需类别”和“已实现类别”)只不过是一种承诺。它们表明特定组件需要实现另一个类别接口的对象,或者正在实现一个特定的类别接口。为了做出这个承诺,我们必须添加以下代码
ComCatDrawBlue.h
// ComCatDrawBlue.h : Declaration of the CComCatDrawBlue #ifndef __COMCATDRAWBLUE_H_ #define __COMCATDRAWBLUE_H_ #include "resource.h" // main symbols #include "ComCatDrawCategory.h" ////////////////////////////////////////////////////////////////////////// // CComCatDrawBlue class ATL_NO_VTABLE CComCatDrawBlue : public CComObjectRootEx<CCOMSINGLETHREADMODEL>, public CComCoClass<CCOMCATDRAWBLUE, &CLSID_ComCatDrawBlue>, public IComCatDrawBlue { public: CComCatDrawBlue() { } DECLARE_REGISTRY_RESOURCEID(IDR_COMCATDRAWBLUE) DECLARE_PROTECT_FINAL_CONSTRUCT() BEGIN_COM_MAP(CComCatDrawBlue) COM_INTERFACE_ENTRY(IComCatDraw) COM_INTERFACE_ENTRY(IComCatDrawBlue) END_COM_MAP() BEGIN_CATEGORY_MAP(CComCatDrawBlue) IMPLEMENTED_CATEGORY(CATID_ComCatDrawCategory) END_CATEGORY_MAP() // IComCatDrawBlue public: STDMETHOD(Draw)(HDC hDC); }; #endif //__COMCATDRAWBLUE_H_就是这样。红色组件以完全相同的方式创建。
使用代码
现在我们有了组件,我们想测试它们。测试应用程序非常简单。它在启动时显示一个对话框,其中显示了当前在计算机上注册的实现 IComCatDraw 接口的每个对象。用户选择一个,当 View 的 OnDraw 函数被调用时,它会调用对象的 Draw 函数。
这里的窍门是获取组件列表并获取它们各自的 CLSIDs。这在 Config 对话框的 OnInitDialog 函数中完成。
ConfigDlg.cpp
BOOL 
CConfigDlg::OnInitDialog()
{
    CDialog::OnInitDialog();
    CComPtr pInfo;
    if (FAILED(CoCreateInstance(CLSID_StdComponentCategoriesMgr, NULL,
                 CLSCTX_ALL, IID_ICatInformation, (void**)&pInfo)))
        return FALSE;
    int cIDs = 1;
    CATID IDs[1];
    IDs[0] = CATID_ComCatDrawCategory;
    CComPtr pEnumCLSID = NULL;
    if (FAILED(pInfo->EnumClassesOfCategories(cIDs, IDs, 0, NULL,
              &pEnumCLSID)))
        return FALSE;
    char szFriendlyName[128] ;
    CLSID clsid ;
    while (pEnumCLSID->Next(1, &clsid, NULL) == S_OK)
    {
        if (getFriendlyName(clsid, szFriendlyName,
                    sizeof(szFriendlyName)))
        {
            int index = m_list.AddString(szFriendlyName) ;
            CLSID* pclsid = new CLSID ;
            *pclsid = clsid ;
            m_list.SetItemDataPtr(index, pclsid) ;
        }
    }
    if (m_list.GetCount() > 0)
        m_list.SetCurSel(0) ;
    return TRUE;  // return TRUE unless you set the focus to a control
                  // EXCEPTION: OCX Property Pages should return FALSE
}
  
这使用了组件类别管理器来获取每个组件的 CLSIDs,并将它们添加到列表控件中。有关组件类别管理器的更多详细信息,请参阅 Len Holgate 的文章。
一旦我们选择了 CLSID,创建对象就相当简单了
ComCatAppView.cpp
void CComCatAppView::OnInitialUpdate() { CView::OnInitialUpdate(); CConfigDlg dlg; if (IDOK == dlg.DoModal()) { m_clsid = dlg.m_clsid; HRESULT hr = CoCreateInstance(m_clsid, NULL, CLSCTX_INPROC_SERVER, IID_IComCatDraw, (void**)&m_pDraw); ASSERT(SUCCEEDED(hr)); } }
注意:请确保包含组件类别头文件(定义类别 CATID 的那个)以及 idl 文件的头文件(有关详细信息,请参阅 ComCatDraw.idl 文件的项目设置)。
关注点
这是设计和实现组件类别以创建可插入对象并在应用程序中使用它们的一个非常简单的示例。在第二部分中,我们将讨论实现连接点的对象。
此外,我只调整了调试配置的配置设置。因此,Release 版本将无法正常工作,除非您将其更改为匹配(特别是,include 文件、midl include 以及 ComCatDraw.idl 文件的构建选项)。
历史
- 首次修订:2003 年 6 月 17 日。
- 第二次修订:2003 年 9 月 15 日。
