1. pooshikin

    No comment

    pooshikin
Changes in body
Source | HTML | Preview

はじめに

前回は、VBA翻訳結果のJavaScriptをコーディングしました。(サンプルとしてください)
今回は、WindowsのCOMを利用したExcelのブックを更新するプログラムを作成します。
Excelのブックを更新する手段として、GoogleのV8エンジン、npmのWin32ole、前回使用したExcelJS、js-xlsxなどがありますが、あえてVC++(Visual Studio C++)で直接、Miscrosoft OfficeのCOMに対してブックを更新するプログラムを作ります。MicrosoftのOLEを利用するのが一番手っ取り早いのと、決して枯れた技術ではないので、改めて使用してみます。最終的には、このプログラムをnode.jsで呼ぶように再利用します。

前提

  • OS : Windows7以上
  • PoweShellのターミナルで実行
  • VSCodeでコード編集
  • node.js環境構築済み
  • Visual Studio Comminnuty 2017のインストール済み
    • VC++のCLRをインストールしておくこと

2019-02-18-14-34-53.png

ブックを更新するプログラムを作る

項目 内容 説明
プログラム名 TomCLR.exe ブックを更新するアプリ
引数1 セル1 "A2"
引数2 値1 問題箇所を□で伏字した文字列
引数3 セル2 "A3"
引数4 値2 解答の文字列

1. プロジェクト作成

ファイル(F) → 新規作成(N) → プロジェクト(P)
2019-02-18-14-40-00.png

プロジェクト名とソリューション名

Visual C++ → CLR → CLRコンソールアプリケーション → 名前 (TomCLR入力) → 場所(任意のディレクトリ) → ソリューション名(TomCLR(自動で入力される)) → ソリューションのディレクトリ作成のチェックをはずす。
2019-02-18-14-47-00.png

参照の追加

元のIDE画面 → ソリューションエクスプローラー → 参照にて右クリック →参照の追加を選択
2019-02-18-14-55-00.png

Microsoft Excel 14.0 Object Library 1.7参照追加

COM → タイプライブラリ → Microsoft Excel 14.0 Object Library 1.7 にチェック → OK
2019-02-18-15-01-00.png

参照の確認

参照にタイプライブラリ[Interrop.Microsoft.Office.Interop.Excel.1.7]が追加されます。
2019-02-18-15-06-00.png
これで、C++でExcelのタイプライブラリが使用可能となります。

2. TomCLR.cppコーディング

ウィザードで作成されたTomCLR.cpp

#include "stdafx.h"

using namespace System;

int main(array<System::String ^> ^args)
{
    return 0;
}

1. Windowsの関数参照解決するため、インクルードにwindows.hを追加します。

2. Microsoft Office Interop参照解決するため、mainの前にusing namespace Microsoft::Office::Interop; を追加します。

※ オブジェクトブラウザーからnamespaceを取得します。
2019-02-18-15-50-00.png

#include "stdafx.h"
#include "windows.h" // 1.

using namespace System;
using namespace Microsoft::Office::Interop; // 2.

int main(array<System::String ^> ^args)
{
    return 0;
}

3.mainにExcelのインスタンスオブジェクト作成。

Excel::Application^ Xls = gcnew Excel::Application(); // 3.

4. tomCLRの実行ディレクトリに配置するブック「temp.xlsx」のパスとブック名を編集します。

GetCurrentDirectoryで実行ディレクトリのパスを取得します。 パスの長さが不定なので、MAX_PATHのサイズを確保します。

5. ブック名「temp.xls」は固定、一応128バイトのサイズを確保します。

※ TCHARをcharサイズで取扱います。(UNICODEではなくマルチバイトとします。)
これをやると、128バイト以下のfile名の場合、後ろに空白が設定されるので空白をトリミングします。

6. パスとブック名を結合します。

7. ブックオープンのファイル名がString指定のため、TCHARをStringへ変換します。

8. 空白をトリミングします。

        // カレントパス
    TCHAR path[MAX_PATH];
    GetCurrentDirectory(MAX_PATH, path); // 4.

    // ブック名
    TCHAR file[128] = TEXT("\\temp.xlsx"); // 5.

    // カレントパス + ブック名
    lstrcat(path, file); // 6.

    // TCHAR → String変換s
    String^ pathname = gcnew String(path); // 7.

    // 空白トリミング
    String^ book = pathname->Trim(); // 8.

9. ブックをオープンします。

※ 引数はオブジェクトブラウザーでコピーできます。

2019-02-18-15-55-00.png
2019-02-18-15-57-00.png

// ブックオープン
Excel::Workbook^ wbook = Xls->Workbooks->Open(book, \
        Type::Missing, \
        Type::Missing, \
        Type::Missing, \
        Type::Missing, \
        Type::Missing, \
        Type::Missing, \
        Type::Missing, \
        Type::Missing, \
        Type::Missing, \
        Type::Missing, \
        Type::Missing, \
        Type::Missing, \
        Type::Missing, \
        Type::Missing); // 9.

10. Sheet1のハンドルを作成します。

// シートハンドル
Excel::Worksheet^ wsheet = (Excel::Worksheet^)wbook->Worksheets[1]; // 10.

11. 引数で取得したRange(A2とA3)と値を設定します。

    // セル->Range(args[0]) 値->Value2(args[1])
    wsheet->Range[args[0], Type::Missing]->Value2 = args[1]; // 11.
    // セル->Range(args[2]) 値->Value2(args[3])
    wsheet->Range[args[2], Type::Missing]->Value2 = args[3]; // 11.

12. ブックを上書きで保存します。

    // ブックの上書き保存
    bool flag = true;
    wbook->Close(flag, \
        System::Type::Missing, \
        System::Type::Missing); // 12.

13. Excel Applicationの終了

    // Excel Application 終了
    Xls->Quit(); // 13.

14. COMハンドルのリリース

wsheet/wbook/Xlsをリリースします。
※ オブジェクトブラウザでReleaseComObjectを検索する。
※ GC(ガーベジコレクション)も解放。(2019.02.20 Add)

System::Runtime::InteropServices::Marshal::ReleaseComObject(wsheet); // 14.
System::Runtime::InteropServices::Marshal::ReleaseComObject(wbook); // 14.
System::Runtime::InteropServices::Marshal::ReleaseComObject(Xls); // 14.

System::GC::Collect(); // 14.

上記の全ソースです。
#include "stdafx.h"
#include "windows.h" // 1.

using namespace System;
using namespace Microsoft::Office::Interop; // 2.

int main(array<System::String ^> ^args)
{
    Excel::Application^ Xls = gcnew Excel::Application(); // 3.

    // カレントパス
    TCHAR path[MAX_PATH];
    GetCurrentDirectory(MAX_PATH, path); // 4.

    // ブック名
    TCHAR file[128] = TEXT("\\temp.xlsx"); // 5.

    // カレントパス + ブック名
    lstrcat(path, file); // 6.

    // TCHAR → String変換s
    String^ pathname = gcnew String(path); // 7.

    // 空白トリミング
    String^ book = pathname->Trim(); // 8.

    // ブックオープン
    Excel::Workbook^ wbook = Xls->Workbooks->Open(book, \
        Type::Missing, \
        Type::Missing, \
        Type::Missing, \
        Type::Missing, \
        Type::Missing, \
        Type::Missing, \
        Type::Missing, \
        Type::Missing, \
        Type::Missing, \
        Type::Missing, \
        Type::Missing, \
        Type::Missing, \
        Type::Missing, \
        Type::Missing); // 9.

    // シートハンドル
    Excel::Worksheet^ wsheet = (Excel::Worksheet^)wbook->Worksheets[1]; // 10.

    // セル->Range(args[0]) 値->Value2(args[1])
    wsheet->Range[args[0], Type::Missing]->Value2 = args[1]; // 11.
    // セル->Range(args[2]) 値->Value2(args[3])
    wsheet->Range[args[2], Type::Missing]->Value2 = args[3]; // 11.

    // ブックの上書き保存
    bool flag = true;
    wbook->Close(flag, \
        System::Type::Missing, \
        System::Type::Missing); // 12.

    // Excel Application 終了
    Xls->Quit(); // 13.

    // COMハンドルリリース
    System::Runtime::InteropServices::Marshal::ReleaseComObject(wsheet); // 14.
    System::Runtime::InteropServices::Marshal::ReleaseComObject(wbook); // 14.
    System::Runtime::InteropServices::Marshal::ReleaseComObject(Xls); // 14.

    System::GC::Collect(); // 14.

    return 0;
}

3. ビルド

ビルド(B) → ソリューションのビルド(B)
2019-02-19-09-52-00.png

ビルドが成功すると、「TomCLR.exe」が作成されます。
TomCLR.exeと同じディレクトリにInterop.Microsoft.Office.Interop.Excel.1.7.dllが作成されます。このDLLはTomCLR.exeとセットでリリースします。

Mode                LastWriteTime     Length Name
----                -------------     ------ ----
-a---        2019/02/20     13:28     100352 TomCLR.exe
-a---        2019/02/20     10:21    1532928 Interop.Microsoft.Office.Interop.Excel.1.7.dll

4. 実行

C:\~\TomCLR\Debug\に前回準備したブック「temp.xlsx」を配置します。
※ 今回はJavaScriptから直接呼び出しをおこなわないで、前回のnode.jsの結果を、「temp.xlsx」のセル(A2)とセル(A3)に設定します。

C:\~\TomCLR\Debug>TomCLR.exe "A2" "徳川
家康(とくがわ いえやす、旧字体: 德川家康)または□□ □□(□□□□□□□□□
)は、戦国時代から安土桃山時代にかけての武将・戦国大名[1]。安祥松平家九代当主。
江戸幕府の初代征夷大将軍[1]。三英傑の一人。「海道一の□□□」の異名を持つ。 " "A
3" "松平 元康,まつだいらもとやす,弓取り"

C:\~\TomCLR\Debug>

5.結果

2019-02-19-10-14-00.png

Node側から呼び出し

(2019.02.20 Add)
runexcel.jsを改修しました。

  • .then(function(val) { の記述を .then((val) => { のコーディングに改めました。
  • child_processライブラリーのspawnを利用してTomCLR.exeを実行します。
  • 関数:AnsColor/関数:AnzColorからの戻り値の文字列に含まれる「半角スペース」を「全角スペース」に変換します。
if( process.argv[2] == undefined || process.argv[3] == undefined ) {
    console.log( '引数を指定してください!' );
    return;
};

const AnsColor = require('./tomexcel.js').AnsColor;
const AnzColor = require('./tomexcel.js').AnzColor;
const spawn = require('child_process').spawn;

// 半角スペース → 全角スペース
const chgSpcHF = (val) => {
  return val.replace(/ /g," ");
}

// 非同期のため処理する順番を制御する
const run = () => {
    let buf = [1];
    AnsColor(process.argv[2], process.argv[3]).then((val) => {
      buf[0] = chgSpcHF(val);
    }).then(() => {
        AnzColor(process.argv[2], process.argv[3]).then((val)  => {
          buf[1] = chgSpcHF(val);
        }).then(() => {
            let param = [1];
            param[0] = 'A2';
            param[1] = 'A3';
            spawn('.\\TomCLR.exe', [param[0], buf[0], param[1], buf[1]], { shell: true }, (err, stdout, stderr) => {
                if (err) { console.log(err); }
                console.log(stdout);
            });
        })
    }).catch(err => console.log(err));
};

run();

実行

PS C:\~\tom> node runexcel.js "A1" "FFFF0000"

まとめ

C++でExcelのCOMを利用したプログラムを作成しました。
COMを利用するとブックのI/Oのオーバーヘッドが発生します。限られたリソースを使用するので、必ずCOMで使用したハンドルはリリースします。Windowsのお約束です。
前回、ExcelJSでブックの読みとりをしたコードもC++に置き換えもできます。
これでこの翻訳プロジェクトの機能部分の主役が揃いました。次回から本格化か?