最近,需要编写具有相似接口的组件,并在特定时间将其中一个插入到应用程序中。尽管实现这一目标的方法有很多,但希望使用一种标准的方法,这种方法既简单又易于编写。由于其背后的基本概念满足了这些需求,并且在业界得到了广泛接受,因此选择了COM。翻阅了所有的书籍,并浏览了互联网,但找不到想要做的指南。感谢Len Holgate的帮助,他的两篇文章(链接在上面)帮助开始了这个项目。
这是本系列的第一部分。在这部分中,将演示一个非常简单的接口,它在两个不同的COM对象中以不同的方式实现。
没有足够的时间和空间来全面概述COM和/或ATL,因此假设至少熟悉这些概念。本文将重点介绍什么是可重用接口,什么是组件类别,以及如何在应用程序中使用它们。
什么是可重用接口?它是一个由多个对象使用的接口,这些对象可能以不同的方式实现它。
下面是一个简单的接口定义,它直接从IUnknown派生,但可以派生自任何间接派生自IUnknown的接口(例如IDispatch)。这是一个非常简单的接口,只有一个方法(Draw)。将在第二部分中查看更复杂的接口。这个接口将是组件的基础接口。
// 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);
};
现在有了通用接口,这很好,但现在如何使用它呢?首先,需要创建一个简单的COM对象,并将其接口从通用接口派生。使用了ATL COM AppWizard来完成这个任务(尽管,如果真的想的话,也可以手动完成)。下面是蓝色组件的派生接口。
// ComCatBlue.idl : IDL source for ComCatBlue.dll
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
{
};
注意变化。由于在基类中声明了Draw方法,所以不需要在IComCatDrawBlue接口中声明它。目前还不能编译项目,因为还没有实现这个函数(如果尝试编译,将会得到链接器错误)。要解决这个问题,需要在实现类中添加以下代码:
// 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
DEFINE_GUID(CATID_ComCatDrawCategory, 0xc49a2274, 0x8d1f, 0x47b9, 0x84, 0x76, 0x82, 0x50, 0x17, 0x49, 0x56, 0xeb);
这本身对没有帮助。但如果在实现类头文件中创建一个类别映射,对象将被注册为实现了所述类别。如果使用不当,这并不一定意味着什么。类别注册表键("Required Categories"和"Implemented Categories")只不过是一个承诺。它们表明一个特定的组件要么需要一个实现了另一个类别接口的对象,要么是实现了特定的类别接口。要实现这个承诺,必须添加以下代码:
// ComCatDrawBlue.h
class ATL_NO_VTABLE CComCatDrawBlue : public CComObjectRootEx, public CComCoClass, 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);
};
这就是全部。红色组件的创建方式完全相同。
现在有了组件,想要测试它们。测试应用程序非常简单。它在启动时显示一个对话框,显示计算机上当前注册的实现IComCatDraw接口的每个对象。用户选择一个,当View的OnDraw函数被调用时,它会调用对象的Draw函数。
这里的诀窍是获取组件列表以及它们各自的CLSIDs。这是在Config Dialog的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;
}
这使用了组件类别管理器来获取每个组件的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文件)。
这是一个非常简单的例子,展示了如何设计和实现组件类别以创建可插拔对象并在应用程序中使用它们。在第二部分中,将查看实现连接点的对象。