代码之家  ›  专栏  ›  技术社区  ›  devuxer

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

  •  28
  • devuxer  · 技术社区  · 14 年前

    编辑

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

    我重新讨论了这个问题,因为我意识到,尽管我对注册和处理所有COM对象都非常仔细,但我的Excel实例仍然没有得到正确的处理。事实证明,有一些方法可以创建完全不明显的COM对象(即,即使从不使用两点,也可能丢失COM对象)。另外,即使你很彻底,如果你的项目超过了一定的规模,丢失一个COM对象的几率也接近100%。当这种情况发生时,很难找到你错过的那个。上面链接的问题的答案提供了一些其他技术来确保Excel实例被关闭。同时,我对我的网站做了一个小的(但意义重大的)更新 ComObjectManager (以下)来反映我从上述问题中学到的东西。

    我见过几个例子 Marshal.ReleaseComObject()

    我想知道我是否能逃脱这样的惩罚:

    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);
    }
    

    促使我问这个问题的原因是,作为林客的奉献者,我真的很想做这样的事情:

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

    …但我担心如果我不发布每个工作表,最终会导致内存泄漏或重影进程( ws

    对此有任何见解都将不胜感激。

    更新

    根据目前的答案,听起来我真的需要释放我创建的每个com对象。我利用这个机会建立了一个 上课,让你更容易对付这种头痛。你必须记住使用 Get() 方法每次实例化一个新的com对象时,但如果这样做,它将为您处理其他所有事情。请让我知道,如果你看到任何问题与它(或编辑,并留下评论,如果你能)。代码如下:

    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();
        }
    }
    
    3 回复  |  直到 7 年前
        1
  •  13
  •   EMP    14 年前

    我相信您必须在每个COM对象上调用ReleaseComObject。因为它们不是垃圾收集的,所以父子层次结构并不是真正的等式:即使您释放父对象,它也不会减少任何子对象上的引用计数。

        2
  •  6
  •   logicnp    14 年前

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

        3
  •  2
  •   Maxter    6 年前

    Clean up Excel Interop Objects with IDisposable

    总结一下答案:

    1. 在调试模式下运行应用程序可能会延迟/阻止对 COM对象。
    2. 阻止清理COM对象。就好像你把程序弄坏了一样。
    3. 如果您正确关闭已调试的应用程序,您将看到所有COM对象都已释放。另外,在发布模式下运行应用程序并正确关闭它也会释放所有COM对象。

    现在,如果您想在方法调用结束后立即释放所有COM对象,那么只需调用 GC.Collect(); GC.WaitForPendingFinalizers();

    但是您需要在创建COM对象的方法之外调用它。同样,如果您正在调试应用程序,这可能无法按预期工作,但它将在发布模式下工作。