概要
前々回、前回とVC++
でエクセル
を操作するプログラムを投稿しましたが、今回も引き続きVC++
のプログラムでエクセル
を操作します。
これまでは単体のデータの読み書きを行ってきました。複数のデータを扱うためには、ループで列と行を指定したセルを対象にすればいいのですが、これを大量に行うと非常に遅くなります。
そこで、セルの範囲を指定してまとめて読み書きすることを考えます。セル範囲に書込むデータは二次元配列になりますが、SAFEARRAY
という特別な配列を使用することになります。
SAFEARRAY
の扱いはちょっと面倒なのですが、データの格納と取出しができればとりあえずはいいんじゃないかと思います。
SAFEARRAY の使い方
SAFEARRAY
はCOM
でデータ(配列)をやりとりするときに使われる<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 | 各次元の下限と要素数 |
大切なのはcDims
とrgsabound
です。
次元と要素数が決まれば、配列の構造が分かります。
fFeatures
とcLocks
をプログラムで直接扱うことはないと思われます。
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