ドリリウム

趣味をとことん突き詰めるブログ。ペット / フィンチ / カメラ / 高知 / 釣り / DIY / バイク / 車 / 家具制作 / アクアリウム / コーヒー / 地方移住 / ルノー・ルーテシア / SUZUKI ST250 E type / EOS Kiss M

こんにちは。カタミチ(@katamichi2h)です。

お問い合わせsyufukc@gmail.com / Twitter

【Interop.Excel】Excelプロセス絶対殺すコード

f:id:katamichinijikan:20180722110028p:plain

2019年5月12日加筆修正

.NETで仕事をしていると、どうしてもInterop.Excelを使ったExcel操作が必要な場面が出てきます。

例えばライブラリが使えない事情があったり、ライブラリでは操作できない部分の操作であったり。

 

とにかくそんなInterop.Excelを使うしかない状況下では、どうしても問題になるのがExcelのバッググラウンドプロセスが残るという問題です。

大抵の人は、実装方法を調べるうちにこの問題のことを自然と知るはずです。

それほど広く知られた問題ではありますが、軽く調べただけでは解決しない問題があります。

 

私の場合には、長年Interop.Excelとつかず離れず、使いたくないけどたまに仕方なく使う関係を続けていましたが、どうしても解決しない2つの問題がありました。

それが、

Excelのバッググラウンドプロセスって何者?

正しくReleaseComObjectをしてもプロセスが残る

の2点です。

Interop.Excelの基本

Interop.Excelを使った基本的なExcel操作の方法は、検索をすれば山のようにでてきますが、以下のページが大抵先頭にでてきて、参考になります。

qiita.com

ここではこうした基本を押さえたうえで、どうしてもバックグラウンドプロセスを殺したいのに殺せない問題への対処法を紹介します。

そのうえで、出来ればもっとスマートな方法を知りたいと思っています。

謎のバックグラウンドプロセス

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.Collect メソッド (System)

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を使って頻繁にExcel操作をするプログラムは専用端末で動かして都度Excelプロセスを全部Killしたい。ていうかEPPlusがいい。

▼IT系の転職なら

▼誰かにプログラミングを教えてみませんか?

▼釣り具の宅配買取なら