VC++ でエクセルのセル範囲から配列データを読込む

# 概要
[前回](https://qiita.com/BenjaminBenton7/items/6194b29f4480dc2aaaf7) は配列データをエクセルのワークシートに書込みましたが、今回も引き続きワークシートのセル範囲を配列 (`SAFEARRAY`) に読込みます。
書込みと同様にセル範囲を指定して読込みますが、今回はデータが入力されているセル範囲を取得して読込みます。
読込んだデータは`VARIANT`型なのですが、その内容は`VT_VARIANT | VT_ARRAY`です。

`SAFEARRAY`の使い方に関しては繰り返しませんが、今回は`SafeArrayAccessData()`関数でポインタを取得して直接アクセスします。

# 二次元配列のメモリ配置
`SAFEARRAY`の多次元配列は`C/C++`とは違うメモリ配置になっています。
`int a[4][3]`という二次元配列を例にすると
![メモリ配置](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/207428/62abf2b8-97cc-2eb7-7a2e-50fec0278a2d.png)
という感じです。[^1]
[^1]:ただし、これに関しては`Microsoft`のドキュメントを確認したわけでは無く、私が`Web`の情報などをもとに独自に解析して推定したもので、間違っている可能性もあります。

# サンプルプログラム
前回作成したワークシート
![読込むワークシート](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/207428/4a950b85-0624-74b9-9aa7-54cc45150123.png)
を読込みます。


```C++
#include <windows.h>
#include <iostream>
#include <iomanip>
#include <string>
#include <vector>
#include <comutil.h>

#pragma warning (disable:4192)
//Excelを操作するためのタイプライブラリを読みこむ
//Microsoft Office Object Library
#import "libid:2DF8D04C-5BFA-101B-BDE5-00AA0044DE52" no_auto_exclude auto_rename dual_interfaces
//Microsoft Visual Basic for Applications Extensibillity
#import "libid:0002E157-0000-0000-C000-000000000046" dual_interfaces
//Mcrosoft Excel Object Library
#import "libid:00020813-0000-0000-C000-000000000046" no_auto_exclude auto_search auto_rename dual_interfaces

using namespace Excel;										//Excel ネームスペースを使う

struct StartOle {
	StartOle() { CoInitialize(NULL); }						//COMを初期化
	~StartOle() { CoUninitialize(); }						//COMを閉じる
} _inst_StartOle;

void dump_com_error(_com_error& e)
{
	std::cerr << "Com error!\n";
	std::cerr << "\tCode = " << std::setw(8) << std::hex << e.Error() << '\n';
	std::cerr << "\tCode meaning = " << e.ErrorMessage() << '\n';
	_bstr_t bstrSource(e.Source());
	_bstr_t bstrDescription(e.Description());
	LPCSTR	source = (LPCSTR)bstrSource;
	std::cerr << "\tSource = " << (source ? source : "(NULL)") << "\n";
	LPCSTR descript = (LPCSTR)bstrDescription;
	std::cerr << "\tDescription = " << (descript ? descript : "(NULL)") << "\n";
}

//データ読込み用の配列
using DataArray = std::vector<std::vector<double>>;

// データ読込み
void get_data(_WorksheetPtr& sheet, DataArray& data)
{
	//ワークシート内で使用されているセル範囲を取得
	RangePtr range = sheet->UsedRange;

	//セル範囲からデータを取得
	_variant_t arr = range->Value2;

	SAFEARRAY* psa;									//SAFEARRAY のポインタ
	if (arr.vt & VT_BYREF) {
		psa = *arr.pparray;
	} else {
		psa = arr.parray;
	}

	long rows;
	long cols;
	SafeArrayGetUBound(psa, 1, &rows);				//行数を取得
	SafeArrayGetUBound(psa, 2, &cols);				//列数を取得

	//読込んだデータを引数にセット
	_variant_t *p;									//VARIANT のポインタ
	HRESULT hr = SafeArrayAccessData(psa, (void **)&p);
	if (hr >= 0) {
		for (int i = 0; i < rows; ++i) {			//行のループ
			std::vector<double> line;
			for (int j = 0; j < cols; ++j) {		//列のループ
				_variant_t var;
				var = *(p + j * rows + i);			//ポインタで直接アクセス
				if (var.vt == VT_R8) {				//セルの内容は数値
					line.push_back(var.dblVal);
				} else {							//数値以外
					line.push_back(-1.0);
				}
			}

			data.push_back(line);					//一行分のデータ登録
		}

		SafeArrayUnaccessData(psa);
	}
}

int main(void)
{
	_ApplicationPtr xl;								//エクセル インスタンス

	//---------------------------------------------------------
	//Excelの起動
	HRESULT hr =  xl.CreateInstance(L"Excel.Application");

	if (hr >= 0) {									//エクセル インスタンスの生成を確認
		xl->Visible[0] = VARIANT_TRUE;				//エクセルを表示する
		xl->DisplayAlerts[0] = VARIANT_FALSE;		//警告が出ないように

		try {										//例外を捕捉
			//-------------------------------------------------
			//既存のワークブックを開く
			WorkbooksPtr workbooks = xl->Workbooks;	//ワークブック コレクション
			_WorkbookPtr workbook = workbooks->Open("Sample02.xlsx");

			//アクティブ・シートを取得
			_WorksheetPtr worksheet = xl->ActiveSheet;

			//セル範囲からデータを読込む
			DataArray data;
			get_data(worksheet, data);

			//読込んだデータを表示
			for (auto& v1 : data) {
				for (auto& v2 : v1) {
					std::cout << std::setw(3) << v2;
				}
				std::cout << '\n';
			}

			worksheet.Release();					//COM オブジェクトを解放

			//確認のために一時停止
			::Sleep(2 * 1000);
			xl->WindowState[0] = xlMinimized;		//ウィンドウを最小化
			std::cout << "エクセルの起動およびデータの読込みを確認:";
			std::string s;
			std::getline(std::cin, s);

			//ワークブックを閉じる
			workbook->Close();
			workbook.Release();						//COM オブジェクトを解放
			workbooks.Release();					//COM オブジェクトを解放
		}
		catch (_com_error& e) {						//例外処理
			dump_com_error(e);
		}

		//Excelを閉じる
		xl->Quit();
		xl.Release();								//COM オブジェクトを解放
	} else {										//エクセルを起動できない
		std::cerr << "エクセルを起動できません\n";
	}

	std::cout << "テストプログラムを終了:";
	std::string s;
	std::getline(std::cin, s);
}
```

# プログラムの説明
`main()`での処理はこれまでとあまり変わりません。
エクセルを起動して、指定されたワークブックをオープン、アクティブシートを取得してデータの読込みます。

`set_data()`でデータをセルからデータを読込み、`std::vector<std::vector<double>>`に格納します。

## データが書込まれている範囲を取得
```c++
	//ワークシート内で使用されているセル範囲を取得
	RangePtr range = sheet->UsedRange;

	//セル範囲からデータを取得
	_variant_t arr = range->Value2;
```
`Worksheet::UseRange`でデータが書込まれているセル範囲を取得できます。セル範囲を指定することで、配列にまとめてデータを読込みことができます。

## SAFEARRAYのポインタを取得
```c++
	SAFEARRAY* psa;									//SAFEARRAY のポインタ
	if (arr.vt & VT_BYREF) {
		psa = *arr.pparray;
	} else {
		psa = arr.parray;
	}
```
`VT_BYREF`の場合`*pparray`が`SAFEARRAY`のポインタになります。

## SAFEARRAYの要素数
```c++
	SafeArrayGetUBound(psa, (long)1, &rows);		//行数を取得
	SafeArrayGetUBound(psa, (long)2, &cols);		//列数を取得
```
取り込んだセルの行数・列数を取得します。
`SafeArrayGetUBound()`関数は要素数の上限を取得するので、要素数そのものではないのですが、下限は`1`なのでここでは要素数として扱っています。[^2]
[^2]:下限を`lower`上限を`upper`とすると、要素数は`upper-lower+1`になります。

## 数値を取り込む
`SafeArrayAccessData()`関数でデータのポインタ (`VARIANT *`) を取得し直接アクセスします。取得が終わったら`SafeArrayUnaccessData`でロックを解除します。

`SafeArrayGetElement`関数で各要素を取り込む方法もありますが、ポインタで直接アクセスした方が速度的には有利になります。

`VARIANT`の型 (`vt`) が`VT_R8`の場合のみ`std::vector`に登録します。[^3]
[^3]:なお、文字列は`VT_BSTR`でデータが無いときは`VT_EMPTY`です。

すでに説明した通り、メモリ配置が`C/C++`の場合とは異なります。

# 開発環境および実行環境
- Windows10
- Visual Studio 2019 Community
- Excel 2013