在我的析构函数中释放 Excel 对象

9
我正在使用Microsoft.Interropt.Excel DLL编写Excel类。我完成了所有函数,但是在析构函数中出现了错误。我希望将所有更改保存到文件中并释放所有资源。我想在析构函数中完成这些操作。但是在我的析构函数中,Excel.ApplicationClass、Workbook和Worksheet对象填充了一个异常,异常信息为“无法使用已与其基础RCW分离的COM对象”。因此,我不能保存任何东西,关闭任何东西,因为我无法访问工作簿或工作表对象。
我能否在析构函数中访问类的私有成员?

如果您能告诉我们更多关于您的类的信息,那会很有帮助。析构函数是在什么时候被调用的 - 是在应用程序关闭时还是在其他用户启动的时间? - Ant
3个回答

17

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

关于此问题的最佳文章之一是Microsoft .NET Framework中的自动内存管理垃圾回收。有关终结器的更多信息,请参见 MSDN 中的Finalize Methods and Destructors文章。

我不能在析构函数中安全地访问类的私有成员吗?

不,你不能这样做。

在您的情况下发生的是,当您的对象不再被根直接或间接引用时,您的对象引用的COM对象 -- 也就是由您的私有字段引用的对象 -- 也不再被根引用。(仅仅因为您的对象的字段引用这些COM对象并不能使它们保持活跃状态,因为您的对象不再被根引用或从根追踪,因此这些COM对象也不会从根追踪。) 因此,您的对象和它引用的所有COM对象都已准备好同时进行垃圾回收。稍后,垃圾回收器将清理您的对象并调用其终结器,就像它对每个COM对象(实际上是运行时可调用包装器(RCW))所做的那样。
问题在于,这些对象何时被垃圾回收是不确定的,而且调用终结器的顺序也是不确定的。在这种情况下,运行时可调用包装器也有一个终结器,它会调用Marshal.ReleaseComObject来释放自身,从而使得 COM 对象的引用计数减少,以便可以释放该 COM 对象。但由于终结器的调用顺序是不确定的,很可能你的对象引用的 COM 对象的终结器会在你的对象终结器之前被触发。因此,在你的终结器中的代码有时候可能会起作用,但大多数情况下,你的对象引用的一个或多个运行时可调用包装器已经被调用了终结器,并且底层的 COM 对象已经被释放,而你的终结器还没有执行其代码。
简而言之,通常应避免使用终结器,并且永远不要在终结器内部访问引用类型,因为这些引用类型可能已经被终结。
为了解决您的问题,我会考虑两种不同的可能性:
  1. 在创建它们的同一方法中处理COM对象。我在这里 这里这里 进行了讨论。

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

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

-- Mike


2

我不确定是否编写了错误的代码--尝试遵循这里的示例。我发现当我利用IDisposable模式时,除非我需要处理工作簿事件,否则一切正常。

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

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

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

在我的Dispose中,我必须忽略Workbooks.Close和Quit的错误,因为它们是导致错误的语句。


1
不,你不应该在析构函数中访问任何托管对象,包括COM RCWs。
相反,实现标准的IDisposable模式,并在Dispose(bool)方法中释放你的COM对象,就像释放可处理的托管对象一样。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接