STL ベースのコレクションの実装

更新 : 2007 年 11 月

ATL には、オブジェクトの標準テンプレート ライブラリ (STL: Standard Template Library) ベースのコレクション インターフェイスをすばやく実装できるように、ICollectionOnSTLImpl インターフェイスが用意されています。このクラスの動作を理解するために、このクラスを使用してオートメーション クライアント用に読み取り専用コレクションを実装する、簡単な例 (下記) を実行してみます。

サンプル コードは、ATLCollections サンプルからの引用です。

この例では、次の操作を行います。

  • 新しい簡単なオブジェクトの生成

  • 生成したインターフェイスの IDL ファイルの編集

  • コレクション項目の格納方法と COM インターフェイスによるクライアントへの公開方法を記述する 5 つの typedef の作成

  • コピー ポリシー クラスのための 2 つの typedef の作成

  • 列挙子とコレクションの実装のための typedef の作成

  • コレクションの typedef を使用するための、ウィザード生成 C++ コードの編集

  • コレクションに値を代入するためのコードの追加

新しい簡単なオブジェクトの生成

プロジェクトを新規作成し、[アプリケーションの設定] の [属性] ボックスがオフになっていることを確認します。[クラスの追加] ダイアログ ボックスと 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);

};

これは、オートメーション クライアントを念頭に置いてデザインされた、標準形式の読み取り専用コレクション インターフェイスです。このインターフェイス定義の番号は、次のコメントに対応しています。

  1. オートメーション クライアントは IDispatch::Invoke を通して _NewEnum プロパティにアクセスするため、コレクション インターフェイスは通常デュアルです。ただし、オートメーション クライアントは、vtable によって残りのメソッドにアクセスできるため、できればディスパッチ インターフェイスよりもデュアル インターフェイスを使用してください。

  2. デュアル インターフェイスまたはディスパッチ インターフェイスが実行時に拡張されない場合 (つまり、IDispatch::Invoke によってほかのメソッドやプロパティを提供しない場合) は、定義に nonextensible 属性を適用する必要があります。この属性によって、オートメーション クライアントは、コンパイル時に完全なコード検証を行うことができます。この場合は、インターフェイスを拡張しません。

  3. オートメーション クライアントでこのプロパティを使用できるようにする場合は、DISPID を正しく指定することが重要です(DISPID_NEWENUM にはアンダースコアが 1 つしかない点に注意してください)。

  4. Item プロパティの DISPID には、任意の値を指定できます。ただし、Item は、通常、DISPID_VALUE を使用して Item をコレクションの既定のプロパティにします。このため、オートメーション クライアントは、明示的に指定せずにこのプロパティを参照できます。

  5. Item プロパティの戻り値として使用されるデータ型は、COM クライアントに関する限り、コレクションに格納される項目の型です。インターフェイスは文字列を返します。したがって、標準の COM 文字列型 BSTR を使用する必要があります。この後の説明にもあるように、データを別の形式で内部的に格納できます。

  6. 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::stringstd::vector としてデータを格納します。std::vector は、マネージ配列と同様に動作する STL コンテナ クラスです。std::string は、標準 C++ ライブラリの文字列クラスです。これらのクラスを使用すると、文字列のコレクションを簡単に操作できます。

このインターフェイスが正常に処理されるには Visual Basic のサポートが必須です。このため、_NewEnum プロパティが返す列挙子は、IEnumVARIANT インターフェイスをサポートしている必要があります。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 クラスを使用できますが、独自のコレクションで使用するデータ型をサポートするには、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 は、ICollectionOnSTLImpl から特化したクラスと同義であり、定義済みの IWords インターフェイスを実装し、IEnumVARIANT をサポートする列挙子を提供します。

ウィザード生成コードの編集

次に、IWords でなく CollectionType によって表されるインターフェイスの実装から 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");
}

これで、選択したクライアントを使用してコードをテストできます。

参照

処理手順

ATLCollections サンプル : ICollectionOnSTLImpl、CComEnumOnSTL、およびカスタム コピー ポリシーの各クラスの例

概念

ATL のコレクションと列挙子

ATL のコピー ポリシー クラス