Help us understand the problem. What is going on with this article?

Excel VBAをJavaScriptに翻訳 その7

More than 1 year has passed since last update.

はじめに

前回は、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.xlsx」は固定、一応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++に置き換えもできます。
これでこの翻訳プロジェクトの機能部分の主役が揃いました。次回から本格化か?

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away