.NET Framework(C#, VB.NET) Excelのプロセスが残る

Office 2016 の PIA がないの記事で環境を設定していたところです。 Excelの帳票を出力するアプリケーションを作っていて、よくあるExcelのプロセスが残る問題が発生してしまったので調べました。

環境

  • Windows 10
  • Visual Studio 2013
  • .NET Framework(C#, VB.NET)
  • Excel 2013

参照カウント

.NET FrameworkのアプリケーションからExcelを操作してプロセスが残るという問題は多くあって、検索すれば解決方法はたくさん出てきます。 オブジェクトの1つ1つを変数に受け取って解放すれば良いのはわかっていたのですが・・ クラスになっていたり、関数になっていたり、その引数に設定されていたり、戻り値になっていたり、そのあたりがよく理解できていませんでした。

次のような場合、workbooks2は無意味ですが、これも解放するのか?とか思ってしまっていました。

Excel.Application app = new Excel.Application;
Excel.Workbooks workbooks = app.Workbooks;
Excel.Workbooks workbooks2 = workbooks;

Marshal.ReleaseComObject(workbooks);
Marshal.ReleaseComObject(app);

workbooks2の解放は必要なくて、COMオブジェクトから直接参照したところは解放する必要があるのだと理解しました。

自分の参照カウントの知識はObjective-Cで学んだことなのですが、Objective-Cの方が規約が明確である分、理解しやすかったように思いました。

問題

今回の問題は別のところにあったのですが、ワークブックの閉じる処理がなかったので、閉じる処理をいれてみたらそこでExcelが異常終了することがあったのです。 Excel.Applicationのオブジェクトを解放する前にです。

Excel.Application app = new Excel.Application;
Excel.Workbooks workbooks = app.Workbooks;
Excel.Workbook workbook = workbooks.Workbooks.Add();

// 何かの処理
// 何かの処理
// 何かの処理

// Excel.WorkbookのCloseがない・・・
// けど、Closeを入れてみたらここでExcelが異常終了した
// workbook.Close();
Marshal.ReleaseComObject(workbook);
Marshal.ReleaseComObject(workbooks);
app.Quit();
Marshal.ReleaseComObject(app);

Excelに帳票のテンプレートとなるレイアウトを定義しておいて、それをコピーしながら処理を進めていたのですが、そのコピーする情報が多すぎてExcelが異常終了してしまうという状況になっていたようでした。

普通にExcelを操作している時でもコピーして貼り付けして、Excelを終了しようとした時に「クリップボードに大きな情報があります。この情報をほかのプログラムに貼り付けられるようにしますか?」というメッセージが出ることがあります。 多分その状態が悪影響をしていたのだと思うのですが・・

Excel.Range.Copyってクリップボードを経由するのですね。

対応

次のようにクリップボードをクリアすることでExcelが異常終了することもなくなりました。 Excelのプロセスが残ることもなくなりました。

Excel.Application app = new Excel.Application;
Excel.Workbooks workbooks = app.Workbooks;
Excel.Workbook workbook = workbooks.Workbooks.Add();

// 何かの処理
// 何かの処理
// 何かの処理

// ワークブックを閉じる前にクリップボードをクリアする
// Windows フォーム アプリケーションの場合
System.Windows.Forms.Clipboard.Clear();
// WPF アプリケーションの場合
System.Windows.Clipboard.Clear();

workbook.Close();
Marshal.ReleaseComObject(workbook);
Marshal.ReleaseComObject(workbooks);
app.Quit();
Marshal.ReleaseComObject(app);

終わり

実装的には参照の解放漏れはなさそうなのに・・ と思っていましたが、こんなところに原因があったとは・・ こういう仕様なのでしょうか? そういう仕様が記載されているページをご存知でしたら教えていただきたいです。

注意:コードブロックに記載したコードはCOMオブジェクトの参照カウントを正しく解放していません。参考に記載したリンク先にあるように正しく解放する必要があります。

参考