Visual Studio 2017 Visual C++ : COM 配列 SAFEARRAY と CComSafeArray

#はじめに
Windows には SAFEARRAY というデータ構造がありますが、ATL の CComSafeArray クラスはこの SAFEARRAY のラッパーです。SAFEARRAY を扱うためには、CreateSafeArray 関数など Win32 API 関数がありますが、CComSafeArray クラスを使ったほうがより簡単になります。

SAFEARRAY を使う理由ですが、C++ で書かれたモジュールと他のモジュールとのやり取りが主な目的だそうです。SAFEARRAY はモジュール境界を超えられますが、std::vector はモジュール境界を超えられないので、外部とのやり取りには利用できないそうです。

CComSafeArray の詳しい説明は下のリンクを参照してください。

[MSDN Magazine: C++ - CComSafeArray による C++ でのセーフ配列プログラミングの簡素化](https://msdn.microsoft.com/ja-jp/magazine/mt795188.aspx)

[MSDN: CComSafeArray クラス](https://msdn.microsoft.com/ja-jp/library/3xzbsee8.aspx)

必要なヘッダー： atlsafe.h

#CComSafeArray の構築
CComSafeArray オブジェクトを構築する方法は、以下のように多数あります。空の SAFEARRAY を構築した場合は、Add メソッドや Atach メソッドで後から要素を追加したり設定できます。

配列の長さだけ指定して、中身は後から指定することもできます。配列の添字はデフォルトでは 0 ですが、それ以外の値に変更することもできます。

- CComSafeArray();
- CComSafeArray(const SAFEARRAYBOUND& bound);
- CComSafeArray(ULONG ulCount, LONG lLBound = 0);
- CComSafeArray(const SAFEARRAYBOUND* pBound, UINT uDims = 1);
- CComSafeArray(const CComSafeArray& saSrc);
- CComSafeArray(const SAFEARRAY& saSrc);
- CComSafeArray(const SAFEARRAY* psaSrc);

#CComSafeArray のメソッド
- **CComSafeArray::Add**　Add メソッドは要素を配列の最後に追加します。次のように3種類のバージョンがあります。
 - HRESULT Add(const SAFEARRAY* psaSrc);
 - HRESULT Add(ULONG ulCount, const T* pT, BOOL bCopy = TRUE);
 - HRESULT Add(const T& t, BOOL bCopy = TRUE);
- **HRESULT Attach(const SAFEARRAY* psaSrc);**　SAFEARRAY* 型の psaSrc をこのオブジェクトの配列にアタッチ (psaSrc に設定されます) します。
- **HRESULT CopyFrom(LPSAFEARRAY* ppArray);**　LPSAFEARRAY* ppArray の内容をこのオブジェクトの配列にコピーする (このオブジェクトの配列が ppArray の中身に置き換わる)。
- **HRESULT CopyTo(LPSAFEARRAY* ppArray);**　このオブジェクトの配列のコピーを LPSAFEARRAY* ppArray に作成する。
- **CComSafeArray::Create**　 CComSafeArray オブジェクトを構築する。次の2つのバージョンがある。
 - HRESULT Create(const SAFEARRAYBOUND* pBound, UINT uDims = 1);
 - HRESULT Create(ULONG ulCount = 0, LONG lLBound = 0);
- **HRESULT Destroy();**　CComSafeArray オブジェクトを破棄する。
- LPSAFEARRAY Detach();　このオブジェクトから SAFEARRAY 配列をデタッチする (配列の割り当て解除を行う)。戻り値はデタッチした配列のポインタになる。
- **T& GetAt(LONG lIndex) const;**　配列要素の添字 lIndex で指定した要素を返す。
- **ULONG GetCount(UINT uDim = 0) const;**　配列の要素の数を返す。
- **UINT GetDimensions() const;**　配列の次元を返す。
- **LONG GetLowerBound(UINT uDim = 0) const;**　uDim で指定した次元の下の境界番号 (要素の添字の最小値) を返す。
- **LPSAFEARRAY* GetSafeArrayPtr() throw();**　この CComSafeArray オブジェクトに含まれる SAFEARRAY のポインタを返す。
- **VARTYPE GetType() const;**　SAFEARRAY のデータ型を返す。
 - VT_I1 : char
 - VT_I2 : short
 - VT_I4 : int
 - VT_I4 : long
 - VT_I8 : longlong
 - VT_UI1 : byte
 - VT_UI2 : ushort
 - VT_UI4 : uint
 - VT_UI4 : ulong
 - VT_UI8 : ulonglong
 - VT_R4 : float
 - VT_R8 : double
 - VT_DECIMAL : decimal pointer
 - VT_VARIANT : variant pointer
 - VT_CY : Currency data type
- **LONG GetUpperBound(UINT uDim = 0) const;**　uDim で指定した次元の上の境界番号 (要素の添字の最大値) を返す。
- **bool IsSizable() const;**　このオブジェクトの配列はサイズを変更可能かを返す。
- **LPSAFEARRAY m_psa;**　この CComSafeArray オブジェクトの SAFEARRAY 構造体へのポインタを保持する公開メンバー変数。
- **HRESULT MultiDimGetAt(const LONG* alIndex, T& t);**　多次元配列の要素を得る。結果は t に返される。
- **HRESULT MultiDimSetAt(const LONG* alIndex, const T& t);**　多次元配列の要素をセットする。
- **HRESULT SetAt(LONG lIndex, const T& t, BOOL bCopy = TRUE);**　配列の要素をセットする。
- **CComSafeArray::Resize**　配列をリサイズする。次の2つのバージョンがある。
 - HRESULT Resize(const SAFEARRAYBOUND* pBound);
 - HRESULT Resize(ULONG ulCount, LONG lLBound = 0);

#CComSafeArray の演算子
- **CComSafeArray::operator []**　配列の要素を得る。これは GetAt メソッドと同じである。次の2つのバージョンがある。
 - T& operator[](LONG lIndex) const;
 - T& operator[](int nIndex) const;
- **CComSafeArray::operator =**　代入演算子。次の2つのバージョンがある。
 - ATL::CComSafeArray<T>& operator=(const ATL::CComSafeArray& saSrc);
 - ATL::CComSafeArray<T>& operator=(const SAFEARRAY* psaSrc);

#サンプル
##ヘッダーファイル

```
// stdafx.h : 標準のシステム インクルード ファイルのインクルード ファイル、または
// 参照回数が多く、かつあまり変更されない、プロジェクト専用のインクルード ファイル
// を記述します。
//
#pragma once
#include "targetver.h"
#include <stdio.h>
#include <tchar.h>

// TODO: プログラムに必要な追加ヘッダーをここで参照してください
#include <atlbase.h>
#include <atlsafe.h>
#include <atlstr.h>
```

##CPP ファイル

```C++
// TestCcomSafeArray.cpp : アプリケーションのエントリ ポイントを定義します。
//

#include "stdafx.h"

//
//  CComSafeArray のテスト
//  ======================
int main()
{
	// インデックスが 0 から始まる長さ 100 の配列。中身は 0 で初期化される。
	CComSafeArray ai(100);
	int i;

	// GetAt, SetAt メソッド
	printf_s("%d\n", ai.GetAt(0));
	ai.SetAt(0, 50);
	printf_s("%d\n", ai.GetAt(0));
	// 境界の外に書き込み
	//   DEBUG モードでは Assert が起こる。RELEASE モードでは何もしない。
	ai.SetAt(100, 100);

	// 配列の境界は指定可能
	CComSafeArray<short> as(10, 1);  // インデックスが 1 から始まる長さ 10 の配列。
	printf_s("LB = %d, UB = %d\n", as.GetLowerBound(), as.GetUpperBound());

	// Add メソッド
	as.Add(1);
	as.Add(2);
	printf_s("LB = %d, UB = %d\n", as.GetLowerBound(), as.GetUpperBound());
	printf_s("%d, %d\n", as.GetAt(11), as.GetAt(12));

	// 中身の SAFEARRAY を参照
	LPSAFEARRAY *sa = ai.GetSafeArrayPtr();

	// 終わり
	getchar();
	return 0;
}
```

![SampleCComSafeArray.png](https://qiita-image-store.s3.amazonaws.com/0/62802/79147675-9f91-a5b0-ba55-4094ac237119.png)
Fig.1 サンプルの実行例