EXCEL.EXEプロセスの残留
Excel COM自動化のあと、外部クライアントに渡したCOM参照が返却されずEXCEL.EXEプロセスが終了せずに残り続ける問題。
Excelプロセス残留問題
Excel COM自動化(Excel COM Interop)
C#などの外部プログラムからCOM経由でExcelアプリケーション本体を起動・操作する自動化方式。
RCW(Runtime Callable Wrapper)
.NETがCOMオブジェクトを操作するために生成するプロキシ。COMインターフェイスポインターをキャッシュし、GCに回収されるときにCOMオブジェクトへの参照を解放する。
COMの参照カウント
COMオブジェクトの寿命を管理する仕組みで、外部に渡した参照がすべて返却される(カウントが0になる)までオブジェクトは解放されない。
EXCEL.EXE(Excelの自動化サーバー)
Excelアプリケーション本体のプロセスで、COMの自動化サーバーとして外部クライアントに参照を渡す。
Application.Quit()
Excelに終了してよいと伝えるCOMメソッド。参照の解放そのものではなく、参照が残っていればExcelは終了を待ち続ける。
Marshal.ReleaseComObject
RCWの内部参照カウントをデクリメントし、0になった時点でRCWが握るCOM参照を即座に解放する.NETのAPI。公式には絶対に必要な場合にのみ使うことが推奨される。
GC 3点セット(Collect→WaitForPendingFinalizers→Collect)
RCWの解放をGCに任せ、Excelを触るメソッドを抜けた後にGC.Collect、WaitForPendingFinalizers、GC.Collectの順に呼んで確定的なタイミングで回収させる手法。
2ドットルール
COMオブジェクトに対してドットを2つ以上つなげず、すべての中間オブジェクトを一度変数に受けるという、Office自動化コミュニティの経験則。
匿名の中間COMオブジェクト(中間RCW)
変数に受けずに複合式の中間結果として生成されるRCWで、参照する変数がないため明示的に解放する手段がない。
匿名RCW
メソッド隔離パターン
Excelを触る処理を1つのメソッドに完全に隔離し、RCWをフィールドや戻り値に逃がさないことで、GC 3点セットによる確実な回収を可能にする設計。
Excel操作の1メソッド隔離
using規律化ラッパー(ComScope)
IDisposableでMarshal.ReleaseComObjectの呼び出しを規約化し、using宣言の逆順でCOMオブジェクトを解放させる小さなラッパー。
EXCEL.EXEの強制終了(保険のKill)
解放パターンを実装してもなお残るケースに備え、自分が起動したExcelインスタンスだけを特定して強制終了する最終手段。
Hwnd + GetWindowThreadProcessId によるPID特定
Application.Hwndプロパティでトップレベルウィンドウのハンドルを取得し、GetWindowThreadProcessIdでそのウィンドウを作成したプロセスのIDを得る、自分のExcelインスタンスを特定する方法。
PIDの再利用リスク
WindowsのプロセスIDが再利用されるため、PIDの数値だけを覚えて後から解決すると、その間に対象プロセスが終了して同じPIDが別プロセスに割り当てられ、無関係なプロセスをKillしてしまう危険。
サーバーサイドのOffice自動化
ASP.NET、DCOM、NTサービスなど無人・非対話のクライアントアプリケーションやコンポーネントからOfficeアプリを自動操作すること。Microsoftは推奨せずサポートもしないと明言している。
無人・非対話のOffice Automation
Open XML SDK
ECMA-376/ISO・IEC 29500として標準化されたOfficeのファイル形式を、Excel本体を起動せずに強く型付けされたクラスで読み書きできるMicrosoft製ライブラリ。
ClosedXML
Open XML APIの上にブック・シート・セルという直感的なAPIを被せた、Excelのインストールなしに.xlsx/.xlsmを扱えるMITライセンスのOSSライブラリ。
AutomationSecurity強制無効化(msoAutomationSecurityForceDisable)
プログラムからブックを開く際のマクロセキュリティを設定するプロパティ。msoAutomationSecurityForceDisableを指定すると、警告なしに開いたブックのVBAマクロを無効化する。Excel 4.0(XLM)マクロはこの設定の対象外で、Excelが別のセキュリティ確認で扱う。
外部由来ブックのマクロ実行リスク
COM自動化でWorkbooks.Openを使って外部由来のブックを開くと、マクロ警告なしにVBAが実行され得るリスク。
.NETはExcelなどのCOMオブジェクトをRCW(Runtime Callable Wrapper)というプロキシ経由で操作する。
RCWはCOMの参照カウントの仕組みの上で、GCに回収されるときにCOMオブジェクトへの参照を解放する。
GCによる回収が遅れる、または明示的な解放がない間、RCWがCOM参照を保持し続けEXCEL.EXEプロセスの残留の原因になることがある。
EXCEL.EXEは外部クライアントに渡した参照カウントがすべて返却される(0になる)まで終了しない。
(a)ReleaseComObject派では、Quit()自体は解放ではないため、確実な終了にはMarshal.ReleaseComObjectによる明示的な解放と組み合わせることが前提になる。
(b)GC派では、Quit()自体は解放ではないため、確実な終了には処理を隔離したうえでのGC 3点セットの実行が前提になる。
Marshal.ReleaseComObjectをすべてのCOMオブジェクトへ規律正しく適用すると、GCを待たずに決定的なタイミングでEXCEL.EXEの残留を防げる。
Excelを触る処理を隔離した後にGC 3点セットを実行すると、RCWが本来の仕組みどおり回収されEXCEL.EXEの残留を防げる。
COMオブジェクトへのドットを2つ以上つなげず中間オブジェクトを変数に受ける2ドットルールは、匿名の中間COMオブジェクトの発生を防ぐ。
変数に受けられていない匿名の中間RCWはMarshal.ReleaseComObjectを呼ぶ手段がなく、GCに回収されるまでEXCEL.EXEの残留の原因になることがある。
メソッド隔離パターンは、隔離したメソッドの外でGC 3点セットを呼ぶことを前提とする。
Excel操作を1メソッドに隔離しGCに任せれば、2ドットルールを破っても匿名の中間RCWごと回収される。
usingで規律化したComScopeラッパーはMarshal.ReleaseComObjectの呼び出しを規約化し、解放漏れによるEXCEL.EXEの残留を防ぐ。
処理を1メソッドに隔離しGC 3点セットに任せるパターンを基本とすることが、EXCEL.EXEの残留対策として推奨される。
解放パターンを正しく実装したうえでの最後の保険として、自分のExcelインスタンスだけを特定したプロセスKillの仕込みが推奨される。
保険のプロセスKillは、Application.HwndとGetWindowThreadProcessIdで自分が起動したExcelのPIDを特定することを前提とする。
起動直後にSafeHandleへ触れてOSのプロセスハンドルを明示的に開いておくことで、PIDの再利用による無関係なプロセスの取り違えを防げる。
Excel COM自動化をサーバーサイドのOffice自動化に用いることは、Microsoftが推奨せずサポートもしないと明言している。
対話ユーザーを前提としないサーバー環境でのOffice自動化は、ハングしたプロセスが無限に蓄積する運用問題を招くことがある。
Officeをインストール・起動せずファイルを直接操作するOpen XML SDKが、サーバーサイドでの帳票生成の代替として推奨される。
ClosedXMLはOpen XML APIの上に直感的なインターフェイスを提供するラッパーである。
ClosedXMLはExcel本体を起動せずに.xlsx/.xlsmを操作できるため、EXCEL.EXEプロセスの残留自体が発生しない。
Workbooks.Openの前にAutomationSecurityをmsoAutomationSecurityForceDisableに設定すると、開いたブックのVBAマクロが警告なしに実行されるリスクを抑えられる。Excel 4.0(XLM)マクロはこの設定の対象外で、Excelが別のセキュリティ確認で扱う
COM自動化でWorkbooks.Openを使って外部由来のブックを開く場合、AutomationSecurity強制無効化を設定できる。