C++ 標準ライブラリベースのコレクションの実装
ATL には、C++ 標準ライブラリベースのコレクション インターフェイスをオブジェクトにすばやく実装できるようにするための ICollectionOnSTLImpl
インターフェイスが用意されています。 このクラスがどのように動作するかを理解するために、このクラスを使用して、オートメーション クライアントを対象とする読み取り専用コレクションを実装する (下記の) 簡単な例を使用します。
サンプル コードは、ATLCollections サンプルからのものです。
この手順を完了するには、次のようにします。
生成されたインターフェイスの IDL ファイルを編集する。
コレクション項目の格納方法と、COM インターフェイスを介してクライアントに公開される方法を記述する 5 つの typedef を作成する。
新しい単純なオブジェクトの生成
新しいプロジェクトを作成し、[アプリケーション設定] の [属性] ボックスがオフになっていることを確かめます。 ATL の [クラスの追加] ダイアログ ボックスと [単純なオブジェクトの追加] ウィザードを使用して、Words
という単純なオブジェクトを生成します。 IWords
というデュアル インターフェイスが生成されていることを確認します。 生成されたクラスのオブジェクトは、単語のコレクション (つまり、文字列) を表すために使用されます。
IDL ファイルの編集
ここで、次に示すように、IDL ファイルを開き、IWords
を読み取り専用コレクション インターフェイスにするために必要な 3 つのプロパティを追加します。
[
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 経由で残りのメソッドにアクセスできるため、デュアル インターフェイスはディスパッチインターフェイスよりも適しています。デュアル インターフェイスまたはディスパッチインターフェイスが実行時に拡張されない (つまり、
IDispatch::Invoke
を介して追加のメソッドやプロパティを指定しない) 場合は、nonextensible 属性を定義に適用する必要があります。 この属性を使用すると、コンパイル時にオートメーション クライアントで完全なコードの検証を実行できます。 この場合、インターフェイスを拡張することはできません。オートメーション クライアントでこのプロパティを使用できるようにするには、正しい DISPID が重要です (DISPID_NEWENUM にはアンダースコアが 1 つだけあることに注意してください)。
Item
プロパティの DISPID として任意の値を指定できます。 しかし、Item
では通常、DISPID_VALUE を使用して、コレクションの既定のプロパティにします。 これにより、オートメーション クライアントでは、明示的に名前を付けずにプロパティを参照できます。Item
プロパティの戻り値に使用されるデータ型は、COM クライアントに関する限り、コレクションに格納される項目の型です。 インターフェイスからは文字列が返されます。したがって、標準の COM 文字列型である BSTR を使用する必要があります。 データは、後述のとおり、内部で異なる形式で格納することができます。Count
プロパティの DISPID に使用される値はまったく任意です。 このプロパティには標準の DISPID がありません。
格納および公開用の 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 の作成
現在、この状況で CComEnumOnSTL
および ICollectionOnSTLImpl
クラスを特殊化するために必要なすべてのテンプレート パラメーターが、typedef の形式で提供されています。 特殊化の使用を簡単にするために、次のようにさらに 2 つの typedef を作成します。
typedef CComEnumOnSTL< EnumeratorInterface, &__uuidof(EnumeratorInterface), EnumeratorExposedType, EnumeratorCopyType, ContainerType > EnumeratorType;
typedef ICollectionOnSTLImpl< CollectionInterface, ContainerType, CollectionExposedType, CollectionCopyType, EnumeratorType > CollectionType;
CollectionType
は、前に定義された IWords
インターフェイスを実装し、IEnumVARIANT
をサポートする列挙子を提供する ICollectionOnSTLImpl
の特殊化のシノニムです。
ウィザードで生成されたコードの編集
ここで、次に示すように、IWords
ではなく、CollectionType
typedef によって表されるインターフェイスの実装から 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");
}
ここで、任意のクライアントを使用してコードをテストできます。