ExcelをMarshal.ReleaseComObjectするタイミングは?

解決


トムヤンクン  2009-08-01 22:01:07  No: 146198

VB2008で、Excelファイルの編集を行うプログラムを作成しています。
CreateObject("Excel.Application")(またはGetObject)でExcelを操作し、Excelを非表示にして(Visible = False)
Excelファイルを編集した後は、Excelを表示して(Visible = True)、後で利用者がそのままExcelファイルを利用できるようにしたいのですが、
CreateObject("Excel.Application")等を行った場合は、CreateObjectしたものをMarshal.ReleaseComObjectする必要があるということが
このサイトでも指摘されています。
そこで質問ですが、上記のようにプログラムの中でExcel.Applicationを終了させない場合でもReleaseComObjectは必要でしょうか?
それとも、内部でExcel.Applicationを終了させる以外は、ReleaseComObjectは不要でしょうか?
よろしくお願いします。


特攻隊長まるるう  2009-08-03 21:48:21  No: 146199

必要です。

そもそもなぜ ReleaseComObject が必要かを理解してください。
エクセルオブジェクトを参照した時点で参照カウンタが+1
されます。参照したオブジェクトの数だけそのオブジェクト毎に
解放処理(参照カウンタのデクリメント)が必要です。

更なる注意点として、レイトバインド時に通常の解放処理では
解放できないという報告もあります。
http://hanatyan.sakura.ne.jp/dotnet/Excel08.htm
> 4.変った使い方をした場合


魔界の仮面弁士  2009-08-04 01:35:14  No: 146200

どこまでを解説するかで悩みますが、大原則として「必要」であると思っておいてください。

> VB2008で、Excelファイルの編集を行うプログラムを作成しています。
とりあえず、Excel 2007 + VB2008 の前提で回答しますね。

> 上記のようにプログラムの中でExcel.Applicationを終了させない場合でもReleaseComObjectは必要でしょうか?
Excel.Application を Quit させず、そのまま残しておきたいわけですよね。
Quit させるかどうかに関わらず、その質問には『必要である』と答えておきます。

可能な限り、“自分が増やした参照カウント”は、自身で減ぜねばなりません。
その意味で、Marshal.ReleaseComObject は必ず呼び出さねばなりません。

以下は、Excel を Visible のまま残し、Quit せずにアプリを終了させるだけのアプリです。
終了タイミングを明確にするために、あえて WinForm ではなく、コンソールアプリとして書いてあります。

'【サンプルA】
Module Module1
    Sub Main()
        Dim obj As Microsoft.Office.Interop.Excel.Application
        obj = New Microsoft.Office.Interop.Excel.ApplicationClass()
        obj.Visible = True
        Dim books As Microsoft.Office.Interop.Excel.Workbooks = obj.Workbooks
        Dim book As Microsoft.Office.Interop.Excel.Workbook
        book = books.Add()
        MsgBox("まだ Excel は残っているハズ。ReleaseComObject します。")
        System.Runtime.InteropServices.Marshal.ReleaseComObject(book)
        book = Nothing
        System.Runtime.InteropServices.Marshal.ReleaseComObject(books)
        books = Nothing
        System.Runtime.InteropServices.Marshal.ReleaseComObject(obj)
        obj = Nothing
        MsgBox("ReleaseComObject しました。Excel は消えていますか?")
    End Sub
End Module

上記が基本的な流れとなります。

2 回目のメッセージが表示された時点で、まだ Excel は表示されており、
アプリ終了後も、そのまま Excel は生き残り続けます。
もちろんユーザーが Excel を終了させれば、通常通り、EXCEL.EXE のプロセスも消失します。

なお上記にある Nothing の代入は、行っても行わなくても構いません。
なぜなら、今回使用した book/books/obj 変数は、いずれもメソッド内のローカル変数であるため、
End Sub に到達した時点で変数のスコープが外れるからです。しかし、フィールド変数の場合には、
できるだけ早期に解放される事を明示するために、Nothing を代入しておくことをお奨めします。

さて……以下は蛇足情報です。読み飛ばしてもらっても構いません。

Excel の場合、たとえ Quit していなくても終了してしまうケースがあります。
たとえば、ワークブックを読み込むことなく Excel.Application 単体を利用していた場合です。

'【サンプルB】
Module Module1
    Sub Main()
        Dim obj As Microsoft.Office.Interop.Excel.Application
        obj = New Microsoft.Office.Interop.Excel.ApplicationClass()
        obj.Visible = True
        MsgBox("まだ Excel は残っているハズ。ReleaseComObject します。")
        System.Runtime.InteropServices.Marshal.ReleaseComObject(obj)
        obj = Nothing
        MsgBox("ReleaseComObject しました。Excel は消えていますか?")
    End Sub
End Module

先ほどとの違いは、ワークブックを Add / Open していないという点です。
この場合、2 回目のメッセージが表示された時点で、EXCEL.EXE のプロセスは終了します。

ただし、その Excel が他の場所で使われていた場合(VB.NET/VB6/VBScript 等で GetObject するなど)、
それらの参照が失われるまでの間、EXCEL.EXE のプロセスは終了せずに残り続けます。
(最後の参照が失われた場合、その時点で EXCEL.EXE も終了します)

まぁ、今回は必ずブックを使うと思いますから、EXCEL.EXE のプロセスが消失する心配は
無いと思いますし、逆に EXCEL を終了させたいのであれば、Quit メソッドを呼ぶでしょうから、
あまり気にする必要は無いのかもしれませんけれどね。

さてもう一点。
ここで試しに、あえてサンプルBの ReleaseComObject の行を削ってみたとしましょう。

'【サンプルC】
Module Module1
    Sub Main()
        Dim obj As Microsoft.Office.Interop.Excel.Application
        obj = New Microsoft.Office.Interop.Excel.ApplicationClass()
        obj.Visible = True
        MsgBox("まだ Excel は残っているハズ。ReleaseComObject します。")
        'System.Runtime.InteropServices.Marshal.ReleaseComObject(obj)
        'obj = Nothing
        MsgBox("ReleaseComObject しました。Excel は消えていますか?")
    End Sub
End Module

当然、2 回目のメッセージが表示された時点で、まだ Excel は表示されています。
しかし、アプリ終了後には、EXCEL.EXE のプロセスが消失するかと思います。

実は、COM オブジェクトがメモリから解放されたことを確認するメカニズムとしては、
ReleaseComObject メソッドとは別に、AppDomain オブジェクトという物があるのです。

AppDomain は、アンロード時に自身が増やした分の参照カウントを減ずる仕組みがあります。
また、COM オブジェクトが解放された後、RCW を解放してガベージ コレクションの対象にもできます。
※ RCW : ランタイム呼び出し可能ラッパー (Runtime Callable Wrapper)

たとえば、自分でアプリケーション ドメインを生成することもできます。

'【サンプルD】
Module Module1
    Sub Main()
        Dim apd As AppDomain = AppDomain.CreateDomain("OratorDomain")
        Dim t As Type = GetType(Microsoft.Office.Interop.Excel.ApplicationClass)
        Dim obj As Microsoft.Office.Interop.Excel.Application
        obj = DirectCast(apd.CreateInstanceFromAndUnwrap(t.Assembly.Location, _
                         "Microsoft.Office.Interop.Excel.ApplicationClass"),  _
                         Microsoft.Office.Interop.Excel.ApplicationClass)
        obj.Visible = True
        MsgBox("まだ Excel は残っているハズ。AppDomain を Unload します。")
        obj = Nothing   '←忘れずに。
        AppDomain.Unload(apd)
        MsgBox("AppDomain を Unload しました。Excel は消えていますか?")
    End Sub
End Module

この場合、2 回目のメッセージが表示された時点で、EXCEL.EXE のプロセスは消失しています。
つまり、ReleaseComObject を呼んでいないのに、サンプルBと同様の結果をもたらすという事です。

ただしこの場合、obj = Nothing の代入が必須である事に注意してください。
これを忘れると、AppDomain.Unload しても、そのオブジェクトは回収の対象となりません。
(ただしどちらにしても、End Sub の時点で、サンプルCと同様の理由により、EXCEL.EXE は消失します)

なお、AppDomain の使用は実行コストの増加を招きますし、コード アクセス セキュリティ上の
問題も生じます。そのため、無闇に利用すべきものではありませんが、一応蛇足情報までに。
http://msdn.microsoft.com/ja-jp/library/aa159887.aspx


トムヤンクン  2009-08-05 20:27:00  No: 146201

みなさま、詳しい解説ありがとうございました。
よく分かりました。
感謝いたします。


※返信する前に利用規約をご確認ください。




  


  このエントリーをはてなブックマークに追加