释放COM对象的正确方法

本文关键字:方法 COM 对象 释放 | 更新日期: 2023-09-27 18:06:36

有时当我结束应用程序时,它试图释放一些COM对象,我在调试器中收到一个警告:

检测到RaceOnRCWCleanUp

如果我写一个使用COM对象的类,我是否需要在IDisposable.Dispose中实现IDisposable并调用Marshal.FinalReleaseComObject来正确释放它们?

如果Dispose没有被手动调用,我还需要在终结器中释放它们吗?还是GC会自动释放它们?现在我在终结器中调用Dispose(false),但我想知道这是否正确。

我使用的COM对象也有一个类侦听的事件处理程序。显然,事件是在另一个线程上引发的,所以我如何正确地处理它,如果它在处置类时被触发?

释放COM对象的正确方法

首先-你从来没有必须调用Marshal.ReleaseComObject(...)Marshal.FinalReleaseComObject(...)当做Excel互操作。这是一个令人困惑的反模式,但是任何关于它的信息,包括来自微软的,表明您必须手动从。net释放COM引用的信息都是不正确的。事实是。net运行时和垃圾收集器正确地跟踪和清理COM引用。

第二,如果你想确保COM对进程外COM对象的引用在你的进程结束时被清理(以便Excel进程将关闭),你需要确保垃圾收集器运行。通过调用GC.Collect()GC.WaitForPendingFinalizers()可以正确地做到这一点。调用两次是安全的,end确保循环也被明确清理。

第三,当在调试器下运行时,局部引用将被人为地保持活动状态,直到方法结束(以便局部变量检查工作)。因此,GC.Collect()调用对于从同一方法中清除像rng.Cells这样的对象是无效的。您应该将执行COM互操作的代码从GC清理拆分为单独的方法。

一般格式为:

Sub WrapperThatCleansUp()
    ' NOTE: Don't call Excel objects in here... 
    '       Debugger would keep alive until end, preventing GC cleanup
    ' Call a separate function that talks to Excel
    DoTheWork()
    ' Now Let the GC clean up (twice, to clean up cycles too)
    GC.Collect()    
    GC.WaitForPendingFinalizers()
    GC.Collect()    
    GC.WaitForPendingFinalizers()
End Sub
Sub DoTheWork()
    Dim app As New Microsoft.Office.Interop.Excel.Application
    Dim book As Microsoft.Office.Interop.Excel.Workbook = app.Workbooks.Add()
    Dim worksheet As Microsoft.Office.Interop.Excel.Worksheet = book.Worksheets("Sheet1")
    app.Visible = True
    For i As Integer = 1 To 10
        worksheet.Cells.Range("A" & i).Value = "Hello"
    Next
    book.Save()
    book.Close()
    app.Quit()
    ' NOTE: No calls the Marshal.ReleaseComObject() are ever needed
End Sub

关于这个问题有很多错误的信息和困惑,包括MSDN和StackOverflow上的许多帖子。

最终说服我仔细看看并找出正确的建议是这篇文章https://blogs.msdn.microsoft.com/visualstudio/2010/03/01/marshal-releasecomobject-considered-dangerous/,以及在一些StackOverflow答案的调试器下发现引用保持存活的问题。

根据我使用不同COM对象(进程内或进程外)的经验,我建议每个COM/.NET边界交叉一个Marshal.ReleaseComObject(如果您引用COM对象以检索另一个COM引用)。

我遇到了很多问题,只是因为我决定将COM互操作清理推迟到GC。也请注意,我从来没有使用Marshal.FinalReleaseComObject -一些COM对象是单例的,它不能很好地工作与这样的对象。

禁止在终结器(或众所周知的IDisposable实现中的Dispose(false))内对托管对象执行任何操作。你不能依赖终结器中的任何。net对象引用。您可以释放IntPtr,但不能释放COM对象,因为它可能已经被清理了。

这里有一篇关于这个的文章:http://www.codeproject.com/Tips/235230/Proper-Way-of-Releasing-COM-Objects-in-NET

简而言之:

1) Declare & instantiate COM objects at the last moment possible.
2) ReleaseComObject(obj) for ALL objects, at the soonest moment possible.
3) Always ReleaseComObject in the opposite order of creation.
4) NEVER call GC.Collect() except when required for debugging.

在自然发生GC之前,com引用不会被完全释放。这就是为什么这么多人需要使用FinalReleaseComObject()和GC.Collect()强制对象销毁的原因。

Dispose不会被GC自动调用。当对象被处理时,将调用析构函数(在另一个线程中)。这通常是您可以释放任何非托管内存或com引用的地方。

析构函数:http://msdn.microsoft.com/en-us/library/66x5fx1b.aspx

…当应用程序封装非托管资源(如窗口、文件和网络连接)时,应该使用析构函数释放这些资源。当对象符合销毁条件时,垃圾收集器将运行该对象的Finalize方法。