VC++ でエクセルのセルからデータを読取る

# 概要
[前回](https://qiita.com/BenjaminBenton7/items/52cfcfe493551e3b69f9)「VC++でエクセルを起動する」と言う記事を投稿しましたが、今回はそれの続きです。

前回はエクセルを起動、新規のワークシートを追加して、データ (文字列) をセルに書込み、ファイルに保存するという内容でした。

今回は既存のファイル (ワークブック) を開いて、セルのデータを読込みます。

# エクセル (VBA) でのオブジェクト階層について
エクセルでオブジェクトは階層構造になっています。

まず、アプリケーション (`Application`) があってその下にワークブック (`Workbook`) があり、ワークシート (`Worksheet`)、セル (`Range`) があります。
複数のワークブックやワークシートはコレクション (`Workbooks`,`Worksheets`) と呼ばれます。

`VBA`ではアプリケーション (`Application`) は通常記述しませんし、ワークブック　(`Workbook`) やワークシート (`Worksheet`) もアクティブなものは省略できますが、`VC++`では省略できないので、全て記述する必要があります。

# VARIANT 型について
セルのデータは`VARIANT`型で読込みます、`VBA`ではおなじみですが、`C++`ではあまり扱う機会はないので、知らない人も多いかと思います。
`VARIANT`型を一言で言うと何でも入れられる型と言うことになります。
`VARIANT`型については[VARIANT型を知ってみる](http://marupeke296.com/IKDADV_CPP_VARIANT.html)がわかりやすかったです。

プログラムを見て頂ければ分かると思いますが、`VARIANT`型のラッパークラスである`variant_t`を使っています。`variant_t`を使うと初期化や後始末が楽になります。

# エクセルファイル (ワークブック) 読込み
このようなワークブックを読込みます。
![Excel20220318_000.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/207428/ebd8b59e-0434-020e-b6d7-38555c338045.png)

```c++
#define  WIN32_LEAN_AND_MEAN
#define  _WIN32_WINNT _WIN32_WINNT_WIN7
#include <windows.h>
#include <iostream>
#include <iomanip>
#include <string>
#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";
}

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

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

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

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

			//ワークシートを取得
			SheetsPtr worksheets = workbook->Worksheets;	//シート コレクション
			_WorksheetPtr worksheet = worksheets->Item[1];	//ワークシート (１枚目)
			
			//セルのデータを読込む
			for (int i = 0; i < 8; ++i) {
				variant_t row{ i + 1 };						//行
				variant_t col{ 1 };							//列
				RangePtr cells = worksheet->Cells;			//セル範囲
				RangePtr cell = cells->Item[row][col];		//セル オブジェクト;
				variant_t data = cell->Value2;				//データを読込む

				switch (data.vt) {							//データ型
				case VT_R8:									//数値
					std::cout << data.dblVal << '\n';
					break;
				case VT_BSTR:								//文字列
					std::cout << (const char *)bstr_t(data.bstrVal) << '\n';
					break;
				case VT_EMPTY:								//データ無し
					std::cout << "Empty\n";
					break;
				}
			}

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

			//確認のために一時停止
			::Sleep(4 * 1000);
			excelApp->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を閉じる
		excelApp->Quit();
		excelApp.Release();									//COM オブジェクトを解放
	} else {												//エクセルを起動できない
		std::cerr << "エクセルを起動できません\n";
	}

	std::cout << "テストプログラムを終了:";
	std::string s;
	std::getline(std::cin, s);
}
```
かなりまどろっこしい書き方をしていますが、コレクションとオブジェクトを強調するためにこのように書き方をしています。
もう少し省略した書き方もできますが、`VC++`ではオブジェクトを省略できないので基本的にこのように書きます。

# 実行結果
![CommandPrompt20220318_000.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/207428/a8e35e0a-804d-2c10-7901-c320b0bc6a89.png)

# プログラムの解説
エクセルの起動は前回と同じです。

ワークブックを開くために、`Open`メソッドを使用します。
`Open`メソッドはワークブック オブジェクトを返します。ワークブックにはシート コレクションが含まれますので、一番目のワークシートを取得しています。

なお、シートにはシートにはワークシートの他にグラフシートなども含まれるので、本来はシートの種類を判定する必要があるのですが、ここでは省略してワークシートであると決め打ちしています。

行と列でセルを指定してデータを読込みます。
読込んだデータは`VARIANT`型なので、数値か文字列かを判定して、表示しています。

文字列は`BSTR`型なので、ラッパークラスの`bstr_t`を介して、`char *`に変換しています。

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