JavaScript で Excel を操作する
- カテゴリ:
- JavaScript
- .NET/C#
- コメント数:
- Comments: 0
◆ ClearScript で V8 を使う
◆ V8 経由で COM の Excel を操作する
◆ .NET は COM を自動で解放してくれないみたい
◆ V8 経由で COM の Excel を操作する
◆ .NET は COM を自動で解放してくれないみたい
npm には ExcelJS のようなライブラリがありますが これらは XML から xlsx ファイルを作るものです
Excel そのものを操作するより高速ですが 見た目に拘るようなものの場合 互換性的に問題が出ることもあります
Python には xlwings のようなライブラリがありますし npm にも Excel を使って操作するものは何か無いかと探してみましたが いいものはなさそうでした
一応 Windows には JScript なるものがあり ActiveXObject で Excel.Application のインスタンスを作って操作できたりはします……が やりたくないです
モダンな JavaScript がいいです
ということで C# の ClearScript 経由でやってみました
エラー処理等は手抜きですが 引数で受け取ったファイルを JavaScript として実行するものです
あとは引数に渡す JavaScript ファイルをこんな感じにします
これで動きました
A1 に 100 を入力した out1.xlsx ファイルが作れます
C# で exe を作らないといけない手間はありますが 全体としては結構簡単にできました
どちらも Excel.Application の COM を使うはずなのですけど
以前 JScript で Excel を操作したときのコードを見るとワークシートへのアクセスは関数形式でした
今回の方法だと Item メソッドが必要になります
Worksheets を関数のように使おうとしたら 関数じゃないのでエラーでした
ググると Worksheets をオブジェクトのように [] でアクセスする例もあって いまいちよくわからない挙動です
COM を解放してないとこうなりますが .NET の場合はフレームワークが管理してるので GC のタイミングでリリースされるはずです
実際これまでも C# を使う場合は解放してなくてもプロセスは終了していました
もしかすると ClearScript で COM を追加すると .NET を通さず V8 が操作できるような仕組みがあるとかなんでしょうか?
一旦 V8 を介さず C# コードで直接こんな感じに置き換えました
これでも発生してました
PowerShell でもやってみました
内容は上の C# コードを PowerShell にそのまま置き換えただけです
なのに PowerShell だと Excel プロセスが終了しました
何が違うのでしょうか
PowerShell だけ特殊なことなんてなさそうなのに……と思ったあたりで気づきました
この PowerShell は Windows に組み込みのレガシーな PowerShell です
ということは .NET Framework と .NET の違いがあります
C# でも .NET Framework を使えば COM を解放してくれそうです
試してみると .NET Framework だと Excel プロセスが終了しました
なぜかはよくわからないですが .NET だと COM の自動解放をやってくれないみたいです
さすがに手動で COM の全解放は辛いです
すべての操作でプロパティやメソッドをチェーンで書けないです
上の例でいうと Range は参照を保存せず直接 Value プロパティを取得しています
これだと Range の COM が解放漏れになるので 一旦変数に入れて後から解放する必要があります
これをやると面倒な上にコードがとても読みづらくなります
手作業だと漏れが出ることを考えると 全部の操作をラップして destructor で解放とかにすれば 見た目はマシになりそうですが 準備が面倒すぎて軽い気持ちで使えません
COM 操作する部分は .NET Framework を使ったほうが良さそうですね
Excel そのものを操作するより高速ですが 見た目に拘るようなものの場合 互換性的に問題が出ることもあります
Python には xlwings のようなライブラリがありますし npm にも Excel を使って操作するものは何か無いかと探してみましたが いいものはなさそうでした
一応 Windows には JScript なるものがあり ActiveXObject で Excel.Application のインスタンスを作って操作できたりはします……が やりたくないです
モダンな JavaScript がいいです
ということで C# の ClearScript 経由でやってみました
V8 エンジンの JavaScript で Excel を操作
ClearScript を依存関係に入れて Program.cs をこうしてビルドしますusing Microsoft.ClearScript.V8;
using (var engine = new V8ScriptEngine())
{
engine.AddHostType("Console", typeof(Console));
engine.AddCOMType("Excel", "Excel.Application");
var source = File.ReadAllText(args[0]);
engine.Execute(source);
}
エラー処理等は手抜きですが 引数で受け取ったファイルを JavaScript として実行するものです
あとは引数に渡す JavaScript ファイルをこんな感じにします
const excel = new Excel()
const book = excel.Workbooks.Add()
const sheet = book.Worksheets.Item(1)
sheet.Range("A1").Value = 100
book.SaveAs("out1.xlsx")
excel.Quit()
これで動きました
A1 に 100 を入力した out1.xlsx ファイルが作れます
C# で exe を作らないといけない手間はありますが 全体としては結構簡単にできました
API
ところで ActiveXObject 経由で使う場合と少し API が異なるようですどちらも Excel.Application の COM を使うはずなのですけど
以前 JScript で Excel を操作したときのコードを見るとワークシートへのアクセスは関数形式でした
book.Worksheets(1)
今回の方法だと Item メソッドが必要になります
book.Worksheets.Item(1)
Worksheets を関数のように使おうとしたら 関数じゃないのでエラーでした
ググると Worksheets をオブジェクトのように [] でアクセスする例もあって いまいちよくわからない挙動です
COM の解放
今回 終了しても Excel プロセスが残り続ける問題が起きましたCOM を解放してないとこうなりますが .NET の場合はフレームワークが管理してるので GC のタイミングでリリースされるはずです
実際これまでも C# を使う場合は解放してなくてもプロセスは終了していました
もしかすると ClearScript で COM を追加すると .NET を通さず V8 が操作できるような仕組みがあるとかなんでしょうか?
一旦 V8 を介さず C# コードで直接こんな感じに置き換えました
var type = Type.GetTypeFromProgID("Excel.Application");
dynamic excel = Activator.CreateInstance(type);
dynamic book = excel.Workbooks.Add();
dynamic sheet = book.Worksheets.Item(1);
sheet.Range("A1").Value = 100;
book.SaveAs("out1.xlsx");
book.Close();
excel.Quit();
これでも発生してました
PowerShell でもやってみました
内容は上の C# コードを PowerShell にそのまま置き換えただけです
なのに PowerShell だと Excel プロセスが終了しました
何が違うのでしょうか
PowerShell だけ特殊なことなんてなさそうなのに……と思ったあたりで気づきました
この PowerShell は Windows に組み込みのレガシーな PowerShell です
ということは .NET Framework と .NET の違いがあります
C# でも .NET Framework を使えば COM を解放してくれそうです
試してみると .NET Framework だと Excel プロセスが終了しました
なぜかはよくわからないですが .NET だと COM の自動解放をやってくれないみたいです
さすがに手動で COM の全解放は辛いです
すべての操作でプロパティやメソッドをチェーンで書けないです
上の例でいうと Range は参照を保存せず直接 Value プロパティを取得しています
これだと Range の COM が解放漏れになるので 一旦変数に入れて後から解放する必要があります
これをやると面倒な上にコードがとても読みづらくなります
手作業だと漏れが出ることを考えると 全部の操作をラップして destructor で解放とかにすれば 見た目はマシになりそうですが 準備が面倒すぎて軽い気持ちで使えません
COM 操作する部分は .NET Framework を使ったほうが良さそうですね