实现基于 C++ 标准库的集合
ATL 提供 ICollectionOnSTLImpl
接口,使你能够在对象上快速实现基于 C++ 标准库的集合接口。 你将通过下面的简单示例来了解此类的工作原理,该示例使用此类来实现针对自动化客户端的只读集合。
示例代码摘自 ATLCollections 示例。
为了完成此过程,你将:
创建五个 typedef,用于描述集合项的存储方式以及它们如何通过 COM 接口向客户端公开。
生成一个新的简单对象
创建一个新项目,确保清除“应用程序设置”下的“特性”框。 使用 ATL 的“添加类”对话框和“添加简单对象向导”生成名为 Words
的简单对象。 确保生成名为 IWords
的双重接口。 生成的类的对象将用于表示字词的集合(即字符串)。
编辑 IDL 文件
现在,打开 IDL 文件,添加三个必要的属性以将 IWords
转换成只读集合接口,如下所示:
[
object,
uuid(7B3AC376-509F-4068-87BA-03B73ADC359B),
dual, // (1)
nonextensible, // (2)
pointer_default(unique)
]
interface IWords : IDispatch
{
[id(DISPID_NEWENUM), propget] // (3)
HRESULT _NewEnum([out, retval] IUnknown** ppUnk);
[id(DISPID_VALUE), propget] // (4)
HRESULT Item([in] long Index, [out, retval] BSTR* pVal); // (5)
[id(0x00000001), propget] // (6)
HRESULT Count([out, retval] long* pVal);
};
这是在考虑到自动化客户端的情况下设计的只读集合接口的标准形式。 此接口定义中的带编号注释对应于以下注释:
集合接口通常是双重的,因为自动化客户端通过
IDispatch::Invoke
访问_NewEnum
属性。 但是,自动化客户端可以通过 vtable 访问剩余的方法,因此双重接口比 dispinterface 更有利。如果在运行时不会扩展双重接口或 dispinterface(也就是说,不会通过
IDispatch::Invoke
提供额外的方法或属性),则应将 nonextensible 特性应用于定义。 此特性使自动化客户端能够在编译时执行完整的代码验证。 在这种情况下,不应扩展接口。如果你希望自动化客户端能够使用此属性,则必须确保 DISPID 正确。 (请注意,DISPID_NEWENUM 中只有一个下划线。)
可以提供任何值作为
Item
属性的 DISPID。 但是,Item
通常使用 DISPID_VALUE 来使其成为集合的默认属性。 这样,自动化客户端就可以在不显式为该属性命名的情况下引用它。就 COM 客户端而言,
Item
属性的返回值使用的数据类型是存储在集合中的项的类型。 接口返回字符串,因此你应该使用标准 COM 字符串类型 BSTR。 稍后你将了解到,可在内部以不同的格式存储数据。Count
属性的 DISPID 使用的值完全是任意性的。 此属性没有标准的 DISPID。
创建用于存储和公开数据的 Typedef
定义集合接口后,需要确定如何存储数据,以及如何通过枚举器公开数据。
这些问题的答案能够以许多 typedef 的形式来提供。可以在新建类的头文件顶部附近添加 typedef:
// Store the data in a vector of std::strings
typedef std::vector< std::string > ContainerType;
// The collection interface exposes the data as BSTRs
typedef BSTR CollectionExposedType;
typedef IWords CollectionInterface;
// Use IEnumVARIANT as the enumerator for VB compatibility
typedef VARIANT EnumeratorExposedType;
typedef IEnumVARIANT EnumeratorInterface;
在本例中,数据存储为 std::string 的 std::vector。 std::vector 是一个 C++ 标准库容器类,其行为类似于托管数组。 std::string 是 C++ 标准库的字符串类。 这些类使字符串集合的处理变得简单。
由于 Visual Basic 支持对于此接口的成功至关重要,因此 _NewEnum
属性返回的枚举器必须支持 IEnumVARIANT
接口。 这是 Visual Basic 能够识别的唯一一个枚举器接口。
为复制策略类创建 Typedef
到目前为止创建的 typedef 提供了为枚举器和集合所用的复制类创建更多 typedef 所需的所有信息:
// Typedef the copy classes using existing typedefs
typedef VCUE::GenericCopy<EnumeratorExposedType, ContainerType::value_type> EnumeratorCopyType;
typedef VCUE::GenericCopy<CollectionExposedType, ContainerType::value_type> CollectionCopyType;
在此示例中,可以使用 ATLCollections 示例中的 VCUE_Copy.h 和 VCUE_CopyString.h 定义的自定义 GenericCopy
类。 可以在其他代码中使用此类,但可能需要定义 GenericCopy
的进一步专用化,以支持你自己的集合中使用的数据类型。 有关详细信息,请参阅 ATL 复制策略类。
为枚举和集合创建 Typedef
现在,已经以 typedef 的形式提供了为本场景专用化 CComEnumOnSTL
和 ICollectionOnSTLImpl
类所需的所有模板参数。 为了简化专用化的使用,请如下所示额外创建两个 typedef:
typedef CComEnumOnSTL< EnumeratorInterface, &__uuidof(EnumeratorInterface), EnumeratorExposedType, EnumeratorCopyType, ContainerType > EnumeratorType;
typedef ICollectionOnSTLImpl< CollectionInterface, ContainerType, CollectionExposedType, CollectionCopyType, EnumeratorType > CollectionType;
现在,CollectionType
是实现前面定义的 IWords
接口并提供支持 IEnumVARIANT
的枚举器的 ICollectionOnSTLImpl
专用化的同义词。
编辑向导生成的代码
现在必须从 CollectionType
typedef 而不是 IWords
表示的接口实现派生 CWords
,如下所示:
class ATL_NO_VTABLE CWords :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CWords, &CLSID_Words>,
// 'CollectionType' replaces 'IWords' in next line
public IDispatchImpl<CollectionType, &IID_IWords, &LIBID_NVC_ATL_COMLib, /*wMajor =*/ 1, /*wMinor =*/ 0>
{
public:
DECLARE_REGISTRY_RESOURCEID(IDR_WORDS)
BEGIN_COM_MAP(CWords)
COM_INTERFACE_ENTRY(IWords)
COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()
// Remainder of class declaration omitted.
添加代码以填充集合
唯一剩下的操作就是用数据填充向量。 在此简单示例中,可以在类的构造函数中向集合添加一些字词:
CWords()
{
m_coll.push_back("this");
m_coll.push_back("is");
m_coll.push_back("a");
m_coll.push_back("test");
}
现在,可以使用所选的客户端来测试代码。