使用包装对象来正确清理Excel互操作对象

所有这些问题:

  • 通过.NETclosures时Excel 2007挂起
  • 如何正确清理C#中的Excel互操作对象
  • 如何正确清理C#中的互操作对象

与使用它们后C#不能正确释放Excel COM对象的问题斗争。 解决这个问题主要有两个方向:

  1. 当不再使用Excel时,终止Excel进程。
  2. 注意明确地将每个用于variables的COM对象首先分配给一个variables,并保证每个variables都执行Marshal.ReleaseComObject。

有些人说,2太繁琐了,你是否忘记在代码的某些地方忘记遵守这条规则,总会有一些不确定性。 仍然1似乎肮脏,容易出错,我也想在一个有限的环境试图杀死一个进程可能会引发安全错误。

所以我一直在考虑通过创build另一个模仿Excel对象模型的代理对象模型(对于我来说,实现我实际需要的对象就足够了)来解决2。 原则如下:

  • 每个Excel Interop类都具有包装该类的对象的代理。
  • 代理在其终结器中释放COM对象。
  • 代理模仿Interop类的接口。
  • 最初返回一个COM对象的任何方法都改为返回一个代理。 其他方法只是将实现委托给内部COM对象。

例:

public class Application { private Microsoft.Office.Interop.Excel.Application innerApplication = new Microsoft.Office.Interop.Excel.Application innerApplication(); ~Application() { Marshal.ReleaseCOMObject(innerApplication); innerApplication = null; } public Workbooks Workbooks { get { return new Workbooks(innerApplication.Workbooks); } } } public class Workbooks { private Microsoft.Office.Interop.Excel.Workbooks innerWorkbooks; Workbooks(Microsoft.Office.Interop.Excel.Workbooks innerWorkbooks) { this.innerWorkbooks = innerWorkbooks; } ~Workbooks() { Marshal.ReleaseCOMObject(innerWorkbooks); innerWorkbooks = null; } } 

我的问题特别是:

  • 谁发现这是一个坏主意,为什么?
  • 谁发现这是一个重要的想法? 如果是这样,为什么还没有人实施/发布这样一个模型呢? 这是否只是由于努力,还是我错过了这个想法的杀戮问题?
  • 终结器中的ReleaseCOMObject不可能/不好/容易出错? (我只看到build议把它放在一个Dispose()而不是在终结者 – 为什么?)
  • 如果方法有道理,有什么build议可以改进呢?

在析构函数中执行ReleaseCOMObject是不可能/不好/危险的吗? (我只看到build议把它放在一个Dispose()而不是一个析构函数 – 为什么?)

build议不要把你的清理代码放在终结器中,因为不像C ++中的析构函数,它不是确定性地调用的。 它可能会在对象超出范围后立即调用。 这可能需要一个小时。 它可能永远不会被调用。 一般来说,如果你想处理非托pipe对象,你应该使用IDisposable模式,而不是终结器。

您链接的解决scheme尝试通过显式调用垃圾回收器并等待终结器完成来解决该问题。 这通常不被推荐,但是对于这种特殊情况,有些人认为这是一个可以接受的解决scheme,因为难以跟踪所有创build的临时非托pipe对象。 但明确清理是正确的做法。 然而鉴于这样做的困难,这个“黑客”可能是可以接受的。 请注意,这个解决scheme可能比您提出的想法更好。

相反,如果你想要明确清理,那么“不要在COM对象中使用两个点”指南将帮助你记住每一个你创build的对象的引用,以便在你完成时清理它们。

我们使用MSDN杂志中描述的LifetimeScope类。 使用它正确地清理对象,并与我们的Excel出口工作得很好。 代码可以在这里下载,也包含杂志文章:

http://lifetimescope.codeplex.com/SourceControl/changeset/changes/1266

看看我的项目MS Office的.NET 。 通过本地VB.NET后期绑定能力解决了引用包装对象和本地对象的问题。

我会做什么:

 class ScopedCleanup<T> : IDisposable where T : class { readonly Action<T> cleanup; public ScopedCleanup(T o, Action<T> cleanup) { this.Object = o; this.cleanup = cleanup; } public T Object { get; private set; } #region IDisposable Members public void Dispose() { if (Object != null) { if(cleanup != null) cleanup(Object); Object = null; GC.SuppressFinalize(this); } } #endregion ~ScopedCleanup() { Dispose(); } } static ScopedCleanup<T> CleanupObject<T>(T o, Action<T> cleanup) where T : class { return new ScopedCleanup<T>(o, cleanup); } static ScopedCleanup<ComType> CleanupComObject<ComType>(ComType comObject, Action<ComType> actionBeforeRelease) where ComType : class { return CleanupObject( comObject, o => { if(actionBeforeRelease != null) actionBeforeRelease(o); Marshal.ReleaseComObject(o); } ); } static ScopedCleanup<ComType> CleanupComObject<ComType>(ComType comObject) where ComType : class { return CleanupComObject(comObject, null); } 

使用案例。 注意调用Quit,这似乎是使过程结束所必需的:

 using (var excel = CleanupComObject(new Excel.Application(), o => o.Quit())) using (var workbooks = CleanupComObject(excel.Object.Workbooks)) { ... } 

值得一提的是, codeplex上的Excel刷新服务使用以下逻辑:

  public static void UsingCOM<T>(T reference, Action<T> doThis) where T : class { if (reference == null) return; try { doThis(reference); } finally { Marshal.ReleaseComObject(reference); } }