8

我正在使用 Microsoft.Interropt.Excel DLL 编写一个 Excel 类。我完成了所有功能,但我的析构函数中有错误。我想保存对我的文件的所有更改,并且我想释放所有源代码。我想把它们都放在我的析构函数中。但在我的析构函数中,Excel.ApplicationClass、Workbook 和 Worksheet 对象被一个异常填充,该异常具有消息“无法使用已与其底层 RCW 分离的 COM 对象”。所以我什么都不能保存,什么也不能关闭,因为我无法访问工作簿或工作表对象。

我不能访问 Destructor 中的类私有成员吗?

4

3 回答 3

16

.NET 最接近析构函数的是 .NET 所称的终结器。主要区别在于析构函数通常具有确定性终结(例如,当对象上的引用计数变为零时),而 .NET 终结器在对象不再被引用后的未确定时间被调用。这是由 .NET 垃圾收集器使用根跟踪过程而不是使用简单的引用计数来处理的。

最好的文章之一是垃圾收集:Microsoft .NET Framework 中的自动内存管理。有关终结器的详细信息,请参阅MSDN 中的终结方法和析构函数一文。

我不能访问 Destructor 中的类私有成员吗?

不,你不能安全地这样做。

在您的情况下发生的情况是,当您的对象不再直接或间接被根引用时,您的对象引用的 COM 对象(即私有字段引用的对象)也不会被根引用任何一个。(被对象的字段引用不会使这些 COM 对象保持活动状态,因为您的对象不再被根引用或跟踪,因此,COM 对象也不会从根跟踪。)所以您的对象和它引用的所有 COM 对象都准备好同时进行垃圾回收。一段时间后,垃圾收集器将清理您的对象并调用它的终结器,就像它也会处理 COM 对象一样,每个对象实际上都是一个运行时可调用包装器 (RCW).

问题在于,不仅这些对象何时被垃圾回收的时间不确定,而且终结器的调用顺序也是不确定的。在这种情况下,Runtime Callable Wrapper 也有一个终结器,它在自身上调用Marshal.ReleaseComObject,其结果是减少栅栏 COM 端的引用计数,以便可以释放此 COM 对象。但是由于调用终结器的顺序是不确定的,因此对象引用的 COM 对象的终结器很可能会在对象的终结器之前触发。所以你的终结器中的代码可以有时会起作用,但在大多数情况下,对象引用的一个或多个运行时可调用包装器已经调用了它们的终结器,并且在终结器开始执行其代码之前,底层的 COM 对象将被释放。

简而言之,你应该避免使用终结器,并且你不应该从终结器中访问引用类型,因为这些引用类型可能已经被终结了。

为了纠正您的情况,我会考虑两种不同的可能性:

  1. 在创建它们的同一方法中处理 COM 对象。我在这里这里有几个讨论。

  2. 通过使用IDisposable 接口来启用对象的确定性处置,而不是依赖于非确定性终结器。

有关如何实现 IDisposable 模式的文章,请参阅:

——迈克

于 2010-01-18T21:46:16.973 回答
1

我不确定我是否在编码错误 - 尝试按照此处的示例进行操作。我发现当我利用 IDisposable 模式时,一切正常,除非我需要处理工作簿事件。

在我的场景中,用户可以在关闭应用程序之前关闭工作簿。我已经声明了 Excel 对象 WithEvents 并编写了 WorkbookBeforeClose 处理程序以满足要求。

在这种情况下,当我关闭我的应用程序(并且我已经关闭了 Excel)时,我收到“无法使用已与其底层 RCW 分离的 COM 对象”错误。当调用 Dispose(False) 时,Finalize 中发生错误。

如果我保留使用事件声明的 Excel 对象但不编写任何处理程序,问题就会消失。

在我的 Dispose 中,我不得不吞下我的 Workbooks.Close 和 Quit 的错误,因为它们是导致错误的语句。

于 2010-02-02T22:11:17.433 回答
0

不,您不应该访问析构函数中的任何托管对象:这包括 COM RCW。

相反,实现标准 IDisposable 模式,并在 Dispose(bool) 方法中释放 COM 对象,就像释放一次性托管对象一样。

于 2010-01-18T15:20:03.803 回答