1
0

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

概要

前々回前回VC++エクセルを操作するプログラムを投稿しましたが、今回も引き続きVC++のプログラムでエクセルを操作します。

これまでは単体のデータの読み書きを行ってきました。複数のデータを扱うためには、ループで列と行を指定したセルを対象にすればいいのですが、これを大量に行うと非常に遅くなります。

そこで、セルの範囲を指定してまとめて読み書きすることを考えます。セル範囲に書込むデータは二次元配列になりますが、SAFEARRAYという特別な配列を使用することになります。

SAFEARRAYの扱いはちょっと面倒なのですが、データの格納と取出しができればとりあえずはいいんじゃないかと思います。

SAFEARRAY の使い方

SAFEARRAYCOMでデータ(配列)をやりとりするときに使われる<OAIdl.h>に定義されいる構造体で次のようなものです。

SAFEARRAY 構造体

typedef struct tagSAFEARRAY
    {
    USHORT cDims;
    USHORT fFeatures;
    ULONG cbElements;
    ULONG cLocks;
    PVOID pvData;
    SAFEARRAYBOUND rgsabound[ 1 ];
    } 	SAFEARRAY;
Member 説明
cDims 次元
fFeatures フラグ
cbElements 要素のサイズ
cLocks ロック回数
pvData データのアドレス
rgsabound 各次元の下限と要素数

大切なのはcDimsrgsaboundです。
次元と要素数が決まれば、配列の構造が分かります。
fFeaturescLocksをプログラムで直接扱うことはないと思われます。

SAFEARRAYを扱う関数 (主なもの)

関数 説明
SafeArrayCreate 配列生成
SafeArrayGetDim 配列の次元数を取得
SafeArrayGetLBound 各次元の下限を取得
SafeArrayGetUBound 各次元の上限を取得
SafeArrayGetElement 指定された要素を取得
SafeArrayPutElement データを指定された位置に格納
SafeArrayAccessData ロックしてデータのアドレスを取得
SafeArrayUnaccessData 配列をアンロック
SafeArrayDestroy 配列を破棄

一連の流れ

配列の次元と各下限と要素数を指定して、配列を生成します。
データ アドレスで直接配列にアクセスするか、インデクスを指定してデータを格納あるいは取得します。
使い終わったら配列を破棄します。

メモリ配置

SafeArrayAccessDataを用いれば配列のアドレスを取得できるので、ポインタで直接アクセスできます。ただし、一次元配列は問題ないのですが、二次元以上配列ではC/C++とはメモリ配置が違うので、ちょっと扱いづらいです。

参考 : SAFEARRAY 構造体

サンプルプログラム

#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";
}

// データ書込み
void set_data(_WorksheetPtr &sheet, long rows, long cols)
{
	//SafeArrayデータ生成
	variant_t arr;
	arr.vt = VT_ARRAY | VT_VARIANT;
	SAFEARRAYBOUND sab[2];
	sab[0].lLbound = 1; sab[0].cElements = rows;
	sab[1].lLbound = 1; sab[1].cElements = cols;
	arr.parray = SafeArrayCreate(VT_VARIANT, 2, sab);

	//セルに書込むデータを用意する
	for (int i = 0; i < rows; ++i) {
		for (int j = 0; j < cols; ++j) {
			variant_t val{ (i + 1) * cols + j + 1, VT_I4 };

			long indices[] = { i + 1, j + 1 }; 						//インデックスを設定
			SafeArrayPutElement(arr.parray, indices, (void *)&val); //SAFEARRAYに値を格納
		}
	}

	//セルの範囲を決定
	variant_t vtRowBegin(1); 										//開始行
	variant_t vtColBegin(1); 										//開始列
	variant_t vtRowEnd(rows);										//終了行
	variant_t vtColEnd(cols);										//終了列

	variant_t st = sheet->Cells->Item[vtRowBegin][vtColBegin]; 		//開始セル
	variant_t ed = sheet->Cells->Item[vtRowEnd][vtColEnd]; 			//終了セル

	//セル範囲に書込む
	sheet->Range[st][ed]->Value2 = arr;
}

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->Add((long)xlWorksheet);

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

			//セル範囲にデータを書込む
			set_data(worksheet, 5, 10);

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

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

			//ワークブックを保存
			bstr_t filename{ "Sample02.xlsx" };
			workbook->SaveAs(						//ワークシートを保存
				filename,							//保存するファイル名
				vtMissing,							//ファイルのフォーマットを指定
				vtMissing,							//読み取りパスワードを設定
				vtMissing,							//書き込みパスワードを設定
				vtMissing,							//ファイルを開く時、読み取り専用で開くことを推奨するメッセージを表示するかどうかの設定
				vtMissing,							//バックアップ・ファイルを作成するかどうかの指定
				Excel::xlNoChange					//アクセスモードの指定
			);

			//ワークブックを閉じる
			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()でデータをセルに書込みます。

SAFEARRAY生成

	variant_t arr;
	arr.vt = VT_ARRAY | VT_VARIANT;
	SAFEARRAYBOUND sab[2];
	sab[0].lLbound = 1; sab[0].cElements = rows;
	sab[1].lLbound = 1; sab[1].cElements = cols;
	arr.parray = SafeArrayCreate(VT_VARIANT, 2, sab);

まず、書込み用のVARIANT型の変数を定義します。ここではラッパークラスであるvariant_tを使います。

vtにはVARIANT型の配列を表すVT_ARRAY | VT_VARIANTをセットします。

引数の行数と列数に従って、配列SAFEARRAYを生成します。SafeArrayCreate()関数はメモリの確保に失敗するとNULLを返すので、本来はチェックすべきですが、ここではプログラムを簡単にするために省略しています。

データ作成

	for (int i = 0; i < rows; ++i) {
		for (int j = 0; j < cols; ++j) {
			variant_t val{ (i + 1) * cols + j + 1, VT_I4 };

			long indices[] = { i + 1, j + 1 }; 						//インデックスを設定
			SafeArrayPutElement(arr.parray, indices, (void *)&val); //SAFEARRAYに値を格納
		}
	}

配列が生成できたらデータをセットします。ここではSafeArrayPutElement()関数を使用しています。

セルに書込み

//セルの範囲を決定
	variant_t vtRowBegin(1); 										//開始行
	variant_t vtColBegin(1);										//開始列
	variant_t vtRowEnd(rows);										//終了行
	variant_t vtColEnd(cols);										//終了列

	variant_t st = sheet->Cells->Item[vtRowBegin][vtColBegin]; 		//開始セル
	variant_t ed = sheet->Cells->Item[vtRowEnd][vtColEnd]; 			//終了セル

	//セル範囲に書込む
	sheet->Range[st][ed]->Value2 = arr;

セル範囲の指定はちょっと面倒ですが、Cells->Item[][]を使用して、開始セルと終了セルを決定し、Range[][]->Value2で書込みます

なお、variant_tを使用しているのでデストラクタで配列のメモリは解放されます。使い終わった後にSafeArrayDestroy()関数で破棄する必要はないです。
(SafeArrayDestroy()すると二重解放でエラーになります)

開発環境および実行環境

  • Windows10
  • Visual Studio 2019 Community
  • Excel 2013
1
0
1

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
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?