2010-05-28 70 views
27

编辑是否需要使用Marshal.ReleaseComObject释放*每个* Excel互操作对象?

还请How do I properly clean up Excel interop objects?看到的。我最近遇到了这个问题,它提供了很多关于如何正确处理COM对象的问题。绝对检查超出第一个(标记的)答案,因为其他答案超出了简单的“不要使用两个点”和“为每个COM对象使用ReleaseComObject”的建议。

我首先重新讨论了这个问题,因为我意识到尽管在注册和处理所有COM对象方面非常全面,但我的Excel实例仍然没有正确处理。事实证明,有很多方法可以创建完全不明显的COM对象(即,即使您从不使用两个点,也可能会错过COM对象)。此外,即使你是彻底的,如果你的项目增长超过一定的规模,错过一个COM对象的机会接近100%。当发生这种情况时,很难找到你错过的那个。上面链接的问题的答案提供了一些确保Excel实例完全关闭的其他技巧。同时,我对ComObjectManager(下文)做了一个小的(但是很重要的)更新,以反映我从上面提到的问题中学到了什么。

原始的问题

我见过几个例子Marshal.ReleaseComObject()使用与Excel互操作对象(即,从命名空间中的Microsoft.Office.Interop.Excel对象),但我已经看到了使用各种度。

我想知道如果我能逃脱这样的:

var application = new ApplicationClass(); 
try 
{ 
    // do work with application, workbooks, worksheets, cells, etc. 
} 
finally 
{ 
    Marashal.ReleaseComObject(application) 
} 

或者,如果我需要释放每一个对象创建的,在这个方法:

public void CreateExcelWorkbookWithSingleSheet() 
{ 
    var application = new ApplicationClass(); 
    var workbook = application.Workbooks.Add(_missing); 
    var worksheets = workbook.Worksheets; 
    for (var worksheetIndex = 1; worksheetIndex < worksheets.Count; worksheetIndex++) 
    { 
     var worksheet = (WorksheetClass)worksheets[worksheetIndex]; 
     worksheet.Delete(); 
     Marshal.ReleaseComObject(worksheet); 
    } 
    workbook.SaveAs(
     WorkbookPath, _missing, _missing, _missing, _missing, _missing, 
     XlSaveAsAccessMode.xlExclusive, _missing, _missing, _missing, _missing, _missing); 
    workbook.Close(true, _missing, _missing); 
    application.Quit(); 
    Marshal.ReleaseComObject(worksheets); 
    Marshal.ReleaseComObject(workbook); 
    Marshal.ReleaseComObject(application); 
} 

是什么促使我问这个问题是,作为LINQ的奉献者,我真的很想做这样的事情:

var worksheetNames = worksheets.Cast<Worksheet>().Select(ws => ws.Name); 

...但我担心如果我不释放每个工作表(ws)对象,我将最终发生内存泄漏或幽灵进程。

任何有关这方面的意见将不胜感激。

更新

基于答案为止,这听起来像我确实需要释放每一个COM对象创建。我借此机会创建了一个ComObjectManager课程,以便处理这个令人头疼的问题。每次你实例化一个新的com对象时,你都必须记得使用Get()方法,但如果你这样做,它会为你处理所有其他事情。如果您发现任何问题,请告诉我(或编辑并留下评论,如果可以的话)。下面的代码:

public class ComObjectManager : IDisposable 
{ 
    private Stack<object> _comObjects = new Stack<object>(); 

    public TComObject Get<TComObject>(Func<TComObject> getter) 
    { 
     var comObject = getter(); 
     _comObjects.Push(comObject); 
     return comObject; 
    } 

    public void Dispose() 
    { 
     // these two lines of code will dispose of any unreferenced COM objects 
     GC.Collect(); 
     GC.WaitForPendingFinalizers(); 

     while (_comObjects.Count > 0) 
      Marshal.ReleaseComObject(_comObjects.Pop()); 
    } 
} 

下面是一个使用示例:

public void CreateExcelWorkbookWithSingleSheet() 
{ 
    using (var com = new ComObjectManager()) 
    { 
     var application = com.Get<ApplicationClass>(() => new ApplicationClass()); 
     var workbook = com.Get<Workbook>(() => application.Workbooks.Add(_missing)); 
     var worksheets = com.Get<Sheets>(() => workbook.Worksheets); 
     for (var worksheetIndex = 1; worksheetIndex < worksheets.Count; worksheetIndex++) 
     { 
      var worksheet = com.Get<WorksheetClass>(() => (WorksheetClass)worksheets[worksheetIndex]); 
      worksheet.Delete(); 
     } 
     workbook.SaveAs(
      WorkbookPath, _missing, _missing, _missing, _missing, _missing, 
      XlSaveAsAccessMode.xlExclusive, _missing, _missing, _missing, _missing, _missing); 
     workbook.Close(true, _missing, _missing); 
     application.Quit(); 
    } 
} 
+0

对于负责释放对象的ComObjectManager类+1。 – 2011-03-25 06:25:10

+0

但是,由于编译器在后台创建大量匿名方法,因此ComObjectManager的解决方案可能会导致程序集的大小增加。 – 2011-03-25 06:50:53

+0

与经理的好主意。 – 2011-04-01 13:47:51

回答

13

我相信你将不得不调用ReleaseComObject的每一个COM对象。由于它们不是垃圾收集,所以父子层次并没有真正进入等式:即使你释放父对象,它也不会减少任何子对象的引用计数。

+0

只是想到了一些......这是否适用于返回值类型的Excel Interop函数?例如'myCell.get_Address()'返回一个'string'。 'myColumn.AutoFit()'返回一个'bool'。我能否认为这些不是问题? – devuxer 2010-05-28 21:49:51

+4

是的,你可以假设这些都不是问题。你只需要明确地释放COM对象;标准的.NET类型很好。 (尼斯在你的包装顺便说一句,非常优雅的解决方案。) – 2010-05-29 13:09:02

+0

谢谢,谢谢! :) – devuxer 2010-05-29 17:59:19

6

您应该在代码中使用的每个COM对象上调用Marshal.ReleaseComObject,而不仅仅是主应用程序对象。