Excel VBAをJavaScriptに翻訳 その７

# はじめに
[**前回**](https://qiita.com/pooshikin/items/adc5644fe43ee7c3360c)は、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](https://qiita-image-store.s3.amazonaws.com/0/239597/804d82c6-7a40-0cd8-4058-91f0a82b8241.png)

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

|項目|内容|説明|
|:--|:--|:--|
|プログラム名|TomCLR.exe|ブックを更新するアプリ|
|引数1|セル１|"A2"|
|引数2|値１|問題箇所を□で伏字した文字列|
|引数3|セル２|"A3"|
|引数4|値２|解答の文字列|
## 1. プロジェクト作成
ファイル(F)　→　新規作成(N) → プロジェクト(P)
![2019-02-18-14-40-00.png](https://qiita-image-store.s3.amazonaws.com/0/239597/4e8dd744-c7ee-8bfc-ace9-79fae066c9ee.png)
### プロジェクト名とソリューション名
Visual C++ → CLR → CLRコンソールアプリケーション → 名前 (TomCLR入力) → 場所(任意のディレクトリ) → ソリューション名(TomCLR(自動で入力される)) → ソリューションのディレクトリ作成のチェックをはずす。
![2019-02-18-14-47-00.png](https://qiita-image-store.s3.amazonaws.com/0/239597/8f103b73-0e49-9c64-df54-8ca454d5c629.png)
### 参照の追加
元のIDE画面　→ ソリューションエクスプローラー → 参照にて右クリック　→参照の追加を選択
![2019-02-18-14-55-00.png](https://qiita-image-store.s3.amazonaws.com/0/239597/066483d8-3e20-1a5e-21b5-47b64dae9063.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](https://qiita-image-store.s3.amazonaws.com/0/239597/8ab13f84-4819-e5a7-beba-0aaba317129f.png)
#### 参照の確認
参照にタイプライブラリ[Interrop.Microsoft.Office.Interop.Excel.1.7]が追加されます。
![2019-02-18-15-06-00.png](https://qiita-image-store.s3.amazonaws.com/0/239597/2b2d3efa-23be-a15d-9e8f-a3f23ba9f4a9.png)
これで、C++でExcelのタイプライブラリが使用可能となります。
## 2. TomCLR.cppコーディング
### ウィザードで作成されたTomCLR.cpp

```C++
#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](https://qiita-image-store.s3.amazonaws.com/0/239597/32e1de07-a735-7ac0-5fe2-cb0320e648f7.png)

```C++
#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のインスタンスオブジェクト作成。
```C++
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. 空白をトリミングします。
```C++
        // カレントパス
	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](https://qiita-image-store.s3.amazonaws.com/0/239597/5a683c14-211f-1bee-6e45-00952f7a3dda.png)
![2019-02-18-15-57-00.png](https://qiita-image-store.s3.amazonaws.com/0/239597/5ff07dba-7f8f-6f8c-fa7c-766ced127398.png)

```C++
// ブックオープン
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のハンドルを作成します。
```C++
// シートハンドル
Excel::Worksheet^ wsheet = (Excel::Worksheet^)wbook->Worksheets[1]; // 10.
```
#### 11. 引数で取得したRange(A2とA3)と値を設定します。
```C++
	// セル->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. ブックを上書きで保存します。
```C++
	// ブックの上書き保存
	bool flag = true;
	wbook->Close(flag, \
		System::Type::Missing, \
		System::Type::Missing); // 12.
```
#### 13. Excel Applicationの終了
```C++
	// Excel Application 終了
	Xls->Quit(); // 13.
```
#### 14. COMハンドルのリリース
wsheet/wbook/Xlsをリリースします。
※ オブジェクトブラウザでReleaseComObjectを検索する。
※ GC(ガーベジコレクション)も解放。(2019.02.20 Add)

```C++
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.
```

<details><summary>上記の全ソースです。</summary><div>

```C++
#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;
}
```
</div></details>
## 3. ビルド
ビルド(B) → ソリューションのビルド(B)
![2019-02-19-09-52-00.png](https://qiita-image-store.s3.amazonaws.com/0/239597/a3e087dd-d9e0-55df-edef-370fd21ae50f.png)

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

```Shell
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)に設定します。

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

C:\～\TomCLR\Debug>

```
## 5.結果
![2019-02-19-10-14-00.png](https://qiita-image-store.s3.amazonaws.com/0/239597/ae289154-5c4c-99e1-6b0f-051c63d9422d.png)

# Node側から呼び出し
(2019.02.20 Add)
runexcel.jsを改修しました。

* .then(function(val) {  の記述を .then((val) => { のコーディングに改めました。
* child_processライブラリーのspawnを利用してTomCLR.exeを実行します。
* 関数:AnsColor/関数:AnzColorからの戻り値の文字列に含まれる「半角スペース」を「全角スペース」に変換します。

```JavaScript

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();
```
実行

```Shell
PS C:\～\tom> node runexcel.js "A1" "FFFF0000"
```

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