C#のCOM相互運用(Microsoft Office Interop Excelライブラリ)を使ってExcelファイルを操作するソフトを作成していると、Excelファイルを閉じてもExcelプロセスが終了しない問題が発生することがあります。本記事では、この問題の原因と解決策を紹介します。
原因
原因は、いずれもCOMオブジェクトの解放に関するものとなります。
オブジェクトの参照解放漏れ
Excelオブジェクトが適切に解放されずに残ると、ガベージコレクションの対象とならず、プロセスが終了しません。
public T GetValue<T>(int row, int col)
{
Range cells = m_xlWorkSheet.Cells;
Range range = cells[row, col];
string value = range.Value;
// Marshal.ReleaseComObject(range); ★これらのオブジェクト解放処理が
// Marshal.ReleaseComObject(cells); ★行われていないとプロセスが残る
}
隠れた参照
Excelオブジェクトの処理をピリオド(.)で続けて書くと、その記述の途中で生成されたオブジェクトを解放することができません。
string value = m_xlWorkSheet.Cells[row, col].Value;
// ★ 上記は、以下の2つのオブジェクトを生成されているが、
// ★ 変数に格納していないので解放できない。
// m_xlWorkSheet.Cells … Rangeオブジェクト
// Cells[row, col] … Rangeオブジェクト
ガベージコレクタを呼んでいない
ガベージコレクタ(GC)を呼び出すと、.NETランタイムはヒープ上のメモリから、アプリケーションによって参照されなくなったオブジェクトのメモリを解放します。これを行わないと、オブジェクトの破棄が遅れ、Excelプロセスが残る原因になります。
解決策
Marshal.ReleaseComObjectでCOMオブジェクトを解放
Excelオブジェクトを使用後、明示的にCloseメソッドを呼び出し、Marshal.ReleaseComObjectを使用してCOMオブジェクトを解放します。
public T GetValue<T>(int row, int col)
{
Range cells = m_xlWorkSheet.Cells;
Range range = cells[row, col];
string value = range.Value;
Marshal.ReleaseComObject(range); // ★ 途中に生成されるオブジェクトを
Marshal.ReleaseComObject(cells); // ★ すべて開放する。
}
ガベージコレクタを強制的に実行
- GC.Collect()とGC.WaitForPendingFinalizers()を使用して、ガベージコレクタが参照をクリアできるようにします。
// ガベージコレクタを強制的に実行
GC.Collect();
GC.WaitForPendingFinalizers();
注意点
- Excelオブジェクトを使用する際は、ピリオド(.)の区切りごとに参照を保持し、不要になったらすぐに解放することが重要です。
- GC.Collect()とGC.WaitForPendingFinalizers()は「重い処理」であり、アプリのパフォーマンスに影響します。このため、Excelファイルを閉じた後の一度だけなど、最小限の使用に留めましょう。
まとめ
C#のCOM相互運用(Microsoft Office Interop Excelライブラリ)を使用してExcelファイルを操作する場合は、適切なリソース管理が重要です。Excelのプロセス残りが発生する場合は、上記の原因についてチェックしてみてください。