我在C#(ApplicationClass)中使用Excel互操作,并在finally子句中放置了以下代码:

while (System.Runtime.InteropServices.Marshal.ReleaseComObject(excelSheet) != 0) { }
excelSheet = null;
GC.Collect();
GC.WaitForPendingFinalizers();

尽管这种方法有效,但即使在我关闭Excel之后,Excel.exe进程仍处于后台。它只在我的应用程序被手动关闭后发布。

我做错了什么,或者是否有其他方法可以确保正确处理互操作对象?


当前回答

在其他答案中考虑的三种一般策略中,杀死excel进程显然是一种黑客行为,而调用垃圾收集器是一种野蛮的猎枪方法,旨在补偿COM对象的错误释放。经过大量的实验,并在我的版本不可知和后期绑定包装器中重写COM对象的管理,我得出结论,准确和及时地调用Marshal.ReleaseComObject()是最有效和优雅的策略。不,您永远不需要FinalReleaseComObject(),因为在一个编写良好的程序中,每个COM只获取一次,因此需要减少一次引用计数器。

应确保释放每个COM对象,最好是在不再需要时释放。但是,在退出Excel应用程序后立即释放所有内容是完全可能的,唯一的代价是更高的内存使用率。只要不松开或忘记释放COM对象,Excel将按预期关闭。

该过程中最简单、最明显的帮助是将每个互操作对象包装到实现IDisposable的.NET类中,其中Dispose()方法在其互操作对象上调用ReleaseComObject()。在析构函数中这样做,正如这里所建议的,没有意义,因为析构函数是非确定性的。

下面显示的是我们的包装器方法,它绕过中间Cells成员从WorkSheet中获取单元格。请注意使用后它处理中间对象的方式:

public ExcelRange XCell( int row, int col)
{   ExcelRange anchor, res;
    using( anchor = Range( "A1") )
    {   res = anchor.Offset( row - 1, col - 1 );  }
    return res;
}

下一步可能是一个简单的内存管理器,它将跟踪获得的每个COM对象,并确保在Excel退出后释放它,如果用户希望用一些RAM来换取更简单的代码。

进一步阅读

如何正确释放Excel COM对象,释放COM对象:垃圾收集器与Marshal.RelseaseComObject。

其他回答

为了说明Excel不关闭的原因,即使在读取和创建时为每个对象创建直接引用,也需要使用“For”循环。

For Each objWorkBook As WorkBook in objWorkBooks 'local ref, created from ExcelApp.WorkBooks to avoid the double-dot
   objWorkBook.Close 'or whatever
   FinalReleaseComObject(objWorkBook)
   objWorkBook = Nothing
Next 

'The above does not work, and this is the workaround:

For intCounter As Integer = 1 To mobjExcel_WorkBooks.Count
   Dim objTempWorkBook As Workbook = mobjExcel_WorkBooks.Item(intCounter)
   objTempWorkBook.Saved = True
   objTempWorkBook.Close(False, Type.Missing, Type.Missing)
   FinalReleaseComObject(objTempWorkBook)
   objTempWorkBook = Nothing
Next

这对我正在进行的一个项目起到了作用:

excelApp.Quit();
Marshal.ReleaseComObject (excelWB);
Marshal.ReleaseComObject (excelApp);
excelApp = null;

我们了解到,在使用Excel COM对象时,将其每个引用都设置为空非常重要。这包括单元格、工作表和所有内容。

您需要意识到Excel对您正在运行的文化也非常敏感。

您可能会发现,在调用Excel函数之前,需要将区域性设置为EN-US。这并不适用于所有函数,但适用于其中一些函数。

    CultureInfo en_US = new System.Globalization.CultureInfo("en-US"); 
    System.Threading.Thread.CurrentThread.CurrentCulture = en_US;
    string filePathLocal = _applicationObject.ActiveWorkbook.Path;
    System.Threading.Thread.CurrentThread.CurrentCulture = orgCulture;

即使您正在使用VSTO,这也适用。

有关详细信息:http://support.microsoft.com/default.aspx?scid=kb;我们;第320369季度

我找到了一个有用的通用模板,它可以帮助实现COM对象的正确处置模式,这些对象在超出范围时需要调用Marshal.ReleaseComObject:

用法:

using (AutoReleaseComObject<Application> excelApplicationWrapper = new AutoReleaseComObject<Application>(new Application()))
{
    try
    {
        using (AutoReleaseComObject<Workbook> workbookWrapper = new AutoReleaseComObject<Workbook>(excelApplicationWrapper.ComObject.Workbooks.Open(namedRangeBase.FullName, false, false, missing, missing, missing, true, missing, missing, true, missing, missing, missing, missing, missing)))
        {
           // do something with your workbook....
        }
    }
    finally
    {
         excelApplicationWrapper.ComObject.Quit();
    } 
}

模板:

public class AutoReleaseComObject<T> : IDisposable
{
    private T m_comObject;
    private bool m_armed = true;
    private bool m_disposed = false;

    public AutoReleaseComObject(T comObject)
    {
        Debug.Assert(comObject != null);
        m_comObject = comObject;
    }

#if DEBUG
    ~AutoReleaseComObject()
    {
        // We should have been disposed using Dispose().
        Debug.WriteLine("Finalize being called, should have been disposed");

        if (this.ComObject != null)
        {
            Debug.WriteLine(string.Format("ComObject was not null:{0}, name:{1}.", this.ComObject, this.ComObjectName));
        }

        //Debug.Assert(false);
    }
#endif

    public T ComObject
    {
        get
        {
            Debug.Assert(!m_disposed);
            return m_comObject;
        }
    }

    private string ComObjectName
    {
        get
        {
            if(this.ComObject is Microsoft.Office.Interop.Excel.Workbook)
            {
                return ((Microsoft.Office.Interop.Excel.Workbook)this.ComObject).Name;
            }

            return null;
        }
    }

    public void Disarm()
    {
        Debug.Assert(!m_disposed);
        m_armed = false;
    }

    #region IDisposable Members

    public void Dispose()
    {
        Dispose(true);
#if DEBUG
        GC.SuppressFinalize(this);
#endif
    }

    #endregion

    protected virtual void Dispose(bool disposing)
    {
        if (!m_disposed)
        {
            if (m_armed)
            {
                int refcnt = 0;
                do
                {
                    refcnt = System.Runtime.InteropServices.Marshal.ReleaseComObject(m_comObject);
                } while (refcnt > 0);

                m_comObject = default(T);
            }

            m_disposed = true;
        }
    }
}

参考:

http://www.deez.info/sengelha/2005/02/11/useful-idisposable-class-3-autoreleasecomobject/

关于释放COM对象的一篇很棒的文章是2.5释放COM对象(MSDN)。

我建议的方法是,如果Excel.Interop引用是非本地变量,则将其置空,然后调用GC.Collect()和GC.WaitForPendingFinalizers()两次。将自动处理本地范围的Interop变量。

这消除了为每个COM对象保留命名引用的需要。

以下是文章中的一个示例:

public class Test {

    // These instance variables must be nulled or Excel will not quit
    private Excel.Application xl;
    private Excel.Workbook book;

    public void DoSomething()
    {
        xl = new Excel.Application();
        xl.Visible = true;
        book = xl.Workbooks.Add(Type.Missing);

        // These variables are locally scoped, so we need not worry about them.
        // Notice I don't care about using two dots.
        Excel.Range rng = book.Worksheets[1].UsedRange;
    }

    public void CleanUp()
    {
        book = null;
        xl.Quit();
        xl = null;

        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();
        GC.WaitForPendingFinalizers();
    }
}

这些话直接来自文章:

在几乎所有的情况下,取消RCW引用并强制垃圾收集将正确清理。如果同时调用GC.WaitForPendingFinalizers,垃圾收集将尽可能具有确定性。也就是说,在第二次调用WaitForPending Finalizers返回时,您将非常确定对象何时被清理。作为替代方案,您可以使用Marshal.ReleaseComObject。但是,请注意,您不太可能需要使用此方法。