.NETで仕事をしていると、どうしてもInterop.Excelを使ったExcel操作が必要な場面が出てきます。ライブラリが使えない事情があったり、ライブラリでは操作できない部分の操作であったり。
とにかくそんなInterop.Excelを使うしかない状況下では、どうしても問題になるのがExcelのバッググラウンドプロセスが残るという問題です。広く知られた問題ではありますが、軽く調べただけでは解決しない問題でもあります。
私の場合には、長年Interop.Excelとつかず離れず、使いたくないけどたまに仕方なく使う関係を続けていましたが、どうしても解決しない2つの問題がありました。
それが、
正しくReleaseComObjectをしてもプロセスが残る
Excelのバッググラウンドプロセスって何者?
の2点です。
Interop.Excelの基本
Interop.Excelを使った基本的なExcel操作の方法は、検索をすれば山のようにでてきますが、こちらのページがよく先頭にでてきて参考になります。
ここではこうした基本を押さえたうえで、どうしてもバックグラウンドプロセスを殺したいのに殺せない問題への対処法を紹介します。
そのうえで、出来ればもっとスマートな方法を知りたいと思っています。
謎のバックグラウンドプロセス
Interop.Excelを使った際に残るExcelのプロセスが何者なのか、いまだにわかりません。
通常の操作でExcelやExcelファイルを開いた場合には、バックグラウンドプロセスは起動時に一瞬動きますが、すぐに消え去ります。
しかし、Interop.ExcelでExcelを操作する場合には、バックグラウンドプロセスがずっと残り続け、COMオブジェクトを開放するまで消えません。更にCOMオブジェクトを正しく開放しても残る場合があります。
このバックグラウンドプロセスは、該当ファイルを操作中に殺しても該当ファイルに影響はしません。Process名は「EXCEL」とあるものの、ファイル名などなく、どのファイルと結びついているのか識別できる情報ももっていません。
なお、このバックグラウンドプロセスはアプリケーションプロセスの起動直前に起動しています。今回はどうしても殺せないこのバッググラウンドプロセスを何が何でも殺してやろうというコードの紹介です。
確実にCOMオブジェクトを開放する
まずは基本に立ち返り、確実にCOMオブジェクトを解放します。
通常、COMオブジェクトを開放する場合には以下のように定義したオブジェクトに対し、
var excelApplication = new Microsoft.Office.Interop.Excel.Application();
このようにReleaseComObjectメソッドで解放します。
Marshal.ReleaseComObject(excelApplication);
ここで解放漏れが起きるケースは経験したことがありませんが、海外サイトなどを見て回ると、念のために以下のように記述している例もありました。
while(Marshal.while(Marshal.ReleaseComObject(excelApplication) > 0);
見ての通り、ReleaseComObjectメソッドの戻り値が0になるまでReleaseComObjectを繰り返します。ReleaseComObjectメソッドの戻り値は、引数で渡されたオブジェクトに関連するCOMオブジェクトの参照カウントです。
ガベージコレクションを強制実行する
効果があったためしはないけど、たまに紹介される方法のひとつです。
一連のReleaseComObjectによる解放の後に書きます。
GC.WaitForPendingFinalizers(); GC.Collect();
GC.WaitForPendingFinalizers メソッド (System)
Excelプロセスを直接殺す
EXCELと名の付くすべてのプロセスを殺す方法です。
foreach (var p in Process.GetProcessesByName("EXCEL")) { p.Kill(); }
プログラムを実行する環境で、他にExcelを使わない場合にはこれで良いと思います。
これなら確実に殺しきれます。
Excelのバックグラウンドプロセスを直接殺す
EXCELと名の付くプロセスのうち、バックグラウンドプロセスを殺します。Excelのバックグラウンドプロセスは、MainWindowTitleの有無で判断します。
foreach (var p in Process.GetProcessesByName("EXCEL")) { if (p.MainWindowTitle == "") { p.Kill(); } }
プログラムを実行する環境で、他にExcelを使う場合でも、問題がほとんどでないと思います。ちょうどp.Kill()のタイミングと全く同じタイミングでExcelを起動しちゃうと一緒に落ちるかもしれません。
Excelのバックグラウンドプロセスを直接殺す(狙い撃ち)
以下のコードでExcelプロセスのリストが取得できます。
Process.GetProcessesByName("EXCEL")
更に、MainWindowTitleプロパティを確認することで、バックグラウンドプロセスであるかどうかも確認することができます。
これに加えて、更に確実に今回Interop.Excelで操作したプロセスかどうかを判定したかったのですが、どうにもプロセスを識別する手段が見つかりませんでした。プロセス内の情報をくまなくみても、識別できそうなものがありません。
あるとすれば、StartTimeプロパティで名前の通りプロセスの起動時間が記録されています。
Process.StartTime プロパティ (System.Diagnostics)
しかし、Excelのアプリケーションプロセスとは誤差があり、判断基準としてはちょっと弱いです。
それではInterop.ExcelによるExcel操作前にProcess.GetProcessesByName(“EXCEL”)でプロセスリストを取得し、処理後に再度取得して差分を見てはどうか、とも考えましたが、やはり処理中に外部でExcelが起動する可能性があるためちょっと弱いです。そもそもなんでReleaseComObjectをきっちりやってもプロセスが残るのか、このプロセスが何をやっているのか。謎が深まるばかりです。
結論
結論としてはInterop.Excelを頻繁に使うプログラムは専用端末で動かして、都度すべてのプロセスをKillするとか、そもそもInterop.Excelを使わずにEPPlusなんかを使うしかなさそうです。