こんにちは、Office 開発系サポート 森 健吾 (kenmori) です。

 今回の投稿では、Office オートメーションの実装コードで割り当てたオブジェクトを解放するというテーマにて記載いたします。
.NET Framework 上で動作するカスタム アプリケーションにおいて、Office オートメーションで処理を実装する場合には割り当てたオブジェクトを確実に解放することをお勧めします。

これは、Office が内部的に OLE や DDE などを通じて実施するオブジェクト インスタンス制御 () によって CLR 上で解放漏れ (または解放待ち) のオブジェクトが誤って参照されてしまい、カスタム アプリケーション側の動作に様々な予期せぬ影響を与えることにあります。

※ OLE や DDE などを通じて実施するオブジェクト インスタンス制御や、解放漏れによる影響等については、別途記載を予定しております。

今回は、詳細な上記の詳細な理由や解放しない際の影響等は省略し、オブジェクト解放の正しい実装方法について記載します。

1.     割り当てたオブジェクトを解放する

Office オートメーションで割り当てたオブジェクトについては、自分で解放処理を記載して必ず破棄される必要があります。

例えば、以下の Windows アプリケーションにおけるサンプル コードでは VB.NET で Dim 宣言を使用して Application, Workbook, Worksheet オブジェクトを割り当てております。割り当てたオブジェクトは Marshal.ReleaseComObject メソッドを使用して必ず解放するようにしております。

また、Application インスタンスを解放する際なのですが、Marshal.ReleaseComObject メソッドを使用して参照カウンタをデクリメントしただけでは、プロセスが終了することを保証できませんので、GC.Collect メソッドでガベージコレクトを強制してオブジェクトを解放しています。

 注意 : なお、サンプル コードの簡素化のため、敢えて例外処理等は記載しておりません。

 VB.NET

Imports Excel = Microsoft.Office.Interop.Excel
Imports System.Runtime.InteropServices

Public Class Form1

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

        Dim app As Excel.Application = New Excel.Application()
        Dim workbook As Excel.Workbook = app.Workbooks.Add()
        Dim worksheet As Excel.Worksheet = workbook.Sheets(1)

        worksheet.Range("A1").Value = TextBox1.Text
        workbook.SaveAs("C:\Out\a.xlsx")
        workbook.Close() 
        app.Quit()

        ' オブジェクトを破棄します。

        Marshal.ReleaseComObject(worksheet)
        worksheet = Nothing

        Marshal.ReleaseComObject(workbook)
        workbook = Nothing

        Marshal.ReleaseComObject(app)
        app = Nothing

        ' ガベージ コレクトを強制します。

        GC.Collect()
        GC.WaitForPendingFinalizers()
        GC.Collect()

    End Sub

End Class

 

なお、ガベージ コレクトの処理は若干冗長に見えますが、上記がベスト プラクティスになります。詳細は以下のサイトをご確認ください。

タイトル :  第 5 章 「マネージ コード パフォーマンスの向上」
アドレス : http://msdn.microsoft.com/ja-jp/library/ms998547.aspx
参考箇所 : GC.Collect の呼び出しを避ける

以下に C# の場合のコーディングも併記します。

C#

using Excel = Microsoft.Office.Interop.Excel;
using System.Runtime.InteropServices;

 namespace winExcelSample
{
   public partial class Form1 : Form
    {

        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {

            Excel.Application app = new Excel.Application();
            Excel.Workbook workbook = app.Workbooks.Add(Type.Missing);
            Excel.Worksheet worksheet = (Excel.Worksheet)workbook.Sheets[1];

            worksheet.get_Range("A1", Type.Missing).set_Value(Type.Missing, textBox1.Text);
            workbook.SaveAs(@"C:\Out\a.xlsx", Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Excel.XlSaveAsAccessMode.xlExclusive, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing);
            workbook.Close(Type.Missing, Type.Missing, Type.Missing);
            app.Quit();

             // オブジェクトを破棄します。

            Marshal.ReleaseComObject(worksheet);
            worksheet = null;

            Marshal.ReleaseComObject(workbook);
            workbook = null;

            Marshal.ReleaseComObject(app);
            app = null;

            // ガベージコレクトを強制します。

            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();

        }
    }
}

 なお、コンソール アプリケーション等では、処理が一通り実行された後、プロセスが終了する際に CLR ランタイムが終了するのに合わせて、Application オブジェクトの解放および EXCEL.EXE プロセスの終了が実施されます。そのため、解放漏れがあったとしても、プログラム終了時に解放されるため、ほとんどの場合大きな影響がない傾向があります。

 

2.     イベントで取得したオブジェクトを解放する

Office オートメーションでイベント ハンドラを追加した場合においても、引数として取得されるオブジェクトは解放する必要があります。上述の例と同様に Marshal.ReleaseComObject メソッドで解放を実施しております。

 VB.NET

Public Sub Workbook_Open(ByVal Wb As Excel.Workbook)

    Try
        ' 処理
    Finally

        ' オブジェクトを破棄します。

        Marshal.ReleaseComObject(Wb)
        Wb = Nothing

    End Try

End Sub

 Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click

    Dim app As Excel.Application = New Excel.Application()
    AddHandler app.WorkbookOpen, AddressOf Workbook_Open

    ' 処理

End Sub

 C#

 

public void app_WorkbookOpen(Excel.Workbook Wb)
{

   try
   {
       // 処理
   }
   finally
   {

       // オブジェクトを破棄します。
       Marshal.ReleaseComObject(Wb);
       Wb = null;
   }
}

private void button2_Click(object sender, EventArgs e)
{

   Excel.Application app = new Excel.Application();
   app.WorkbookOpen += new Microsoft.Office.Interop.Excel.AppEvents_WorkbookOpenEventHandler(app_WorkbookOpen);


            // 処理

}

 

以上となります。

 

一般的には、上記の内容をお約束事と覚えていただいて実装を検討することで問題ないと思われます。

なお、ガベージ コレクトの強制など、一般的な開発では推奨されないとされる実装に踏み切るのは極めて大きな抵抗があるという開発者様のために、次回の投稿 (Office オートメーションで割り当てたオブジェクトを解放する - Part2) ではオブジェクト解放漏れが即時解放されない場合に具体的にどのように影響するかについて例を挙げて記載し、いわゆる「特別な事情がある場合」に該当することをご説明します。