0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Posted at

概要

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

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

二次元配列のメモリ配置

SAFEARRAYの多次元配列はC/C++とは違うメモリ配置になっています。
int a[4][3]という二次元配列を例にすると
メモリ配置
という感じです。1

サンプルプログラム

前回作成したワークシート
読込むワークシート
を読込みます。

#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>>に格納します。

データが書込まれている範囲を取得

	//ワークシート内で使用されているセル範囲を取得
	RangePtr range = sheet->UsedRange;

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

Worksheet::UseRangeでデータが書込まれているセル範囲を取得できます。セル範囲を指定することで、配列にまとめてデータを読込みことができます。

SAFEARRAYのポインタを取得

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

VT_BYREFの場合*pparraySAFEARRAYのポインタになります。

SAFEARRAYの要素数

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

取り込んだセルの行数・列数を取得します。
SafeArrayGetUBound()関数は要素数の上限を取得するので、要素数そのものではないのですが、下限は1なのでここでは要素数として扱っています。2

数値を取り込む

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

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

VARIANTの型 (vt) がVT_R8の場合のみstd::vectorに登録します。3

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

開発環境および実行環境

  • Windows10
  • Visual Studio 2019 Community
  • Excel 2013
  1. ただし、これに関してはMicrosoftのドキュメントを確認したわけでは無く、私がWebの情報などをもとに独自に解析して推定したもので、間違っている可能性もあります。

  2. 下限をlower上限をupperとすると、要素数はupper-lower+1になります。

  3. なお、文字列はVT_BSTRでデータが無いときはVT_EMPTYです。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?