Quantcast
Channel: 配列タグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 757

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

$
0
0
概要 前回 は配列データをエクセルのワークシートに書込みましたが、今回も引き続きワークシートのセル範囲を配列 (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です。 ↩

Viewing all articles
Browse latest Browse all 757

Trending Articles