概要
前回 は配列データをエクセルのワークシートに書込みましたが、今回も引き続きワークシートのセル範囲を配列 (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の場合*pparrayがSAFEARRAYのポインタになります。
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
ただし、これに関してはMicrosoftのドキュメントを確認したわけでは無く、私がWebの情報などをもとに独自に解析して推定したもので、間違っている可能性もあります。 ↩
下限をlower上限をupperとすると、要素数はupper-lower+1になります。 ↩
なお、文字列はVT_BSTRでデータが無いときはVT_EMPTYです。 ↩
↧