为什么Excel com对象不从内存中释放,如果我检查它为空

本文关键字:如果 检查 释放 com Excel 对象 内存 为什么 | 更新日期: 2023-09-27 17:50:18

我继承了一个现有的应用程序来维护,并遇到了一个障碍:它不会从内存中释放Excel对象,一旦它完成。我在这里抽出了相关的代码。这是一个使用COM Interop创建Excel工作簿的方法:

using System.Runtime.InteropServices;
using Excel = Microsoft.Office.Interop.Excel;
...
private void CreateWorkbook(string workingDirectory, string fileName, object[,] cellValues)
{
    Excel.Application excel = null;
    Excel.Workbooks workbooks = null;
    Excel.Workbook workbook = null;
    Excel.Sheets worksheets = null;
    Excel.Worksheet worksheet = null;
    Excel.Range startRange = null;
    Excel.Range endRange = null;
    Excel.Range selectedRange = null;
    try
    {
        excel = new Excel.Application() { DisplayAlerts = false, Visible = false, ScreenUpdating = false, EnableAutoComplete = false };
        // Statement which stops the excel instance exiting:
        if(excel == null)
        {
            MessageBox.Show("Excel could not be started, please check your machine has office 2013 installed.");
            return;
        }
        workbooks = excel.Workbooks;
        workbook = workbooks.Add(Type.Missing);
        worksheets = workbook.Sheets;
        worksheet = (Excel.Worksheet)worksheets[1];            
        startCellRange = (Excel.Range)worksheet.Cells[1,1];
        endCellRange = (Excel.Range)worksheet.Cells[1 + cellValues.GetLength(0), 1 + cellValues.GetLength(1)]; 
        selectedCells = worksheet.Range[startCellRange, endCellRange];
        selectedCells.Value2 = cellValues;
        workbook.SaveAs(workingDirectory + fileName, Excel.XlFileFormat.xlOpenXMLWorkbook, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Excel.XlSaveAsAccessMode.xlExclusive, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing);
        workbook.Close(true, Type.Missing, Type.Missing);
        excel.Quit();
    }
    catch(Exception ex)
    {
        try
        {
            // Catch methods should ideally never throw exceptions, so try close the workbook and quit the excel instance in a try/catch block:
            workbook.Close(false, Type.Missing, Type.Missing);
            excel.Quit();
        }
        catch { }
        MessageBox.Show("An error was encountered while trying to create the excel workbook.'n" + ex.Message + (ex.InnerException == null ? "" : "'n" + ex.InnerException.Message));
    }
    finally
    {
        // Now clean up the com interop stuff:
        // ===================================
        if (Marshal.IsComObject(rangeFont)) Marshal.ReleaseComObject(rangeFont);
        if (Marshal.IsComObject(rangeBorders)) Marshal.ReleaseComObject(rangeBorders);
        if (Marshal.IsComObject(selectedRange)) Marshal.ReleaseComObject(selectedRange);
        if (Marshal.IsComObject(endRange)) Marshal.ReleaseComObject(endRange);
        if (Marshal.IsComObject(startRange)) Marshal.ReleaseComObject(startRange);
        if (Marshal.IsComObject(worksheet)) Marshal.ReleaseComObject(worksheet);
        if (Marshal.IsComObject(worksheets)) Marshal.ReleaseComObject(worksheets);
        if (Marshal.IsComObject(workbook)) Marshal.ReleaseComObject(workbook);
        if (Marshal.IsComObject(workbooks)) Marshal.ReleaseComObject(workbooks);
        if (Marshal.IsComObject(excel)) Marshal.ReleaseComObject(excel);
        if (Marshal.IsComObject(excel)) Marshal.FinalReleaseComObject(excel);
        System.Threading.Thread.Sleep(100);
        GC.Collect();
        GC.WaitForPendingFinalizers();
        System.Threading.Thread.Sleep(500);
        GC.Collect();
        GC.WaitForPendingFinalizers();
    }
}  

如果我在excel = new Excel.Application()...之后注释掉if语句,该方法可以很好地工作并处理excel对象。但是当包含if (excel == null)测试时,excel对象不会从内存中释放出来。我一直在想为什么,但我被难住了。我知道运行时可以自动将包装器放在com对象周围(即当使用超过"2点"访问com对象时)。但检查com对象是否等于null似乎不符合运行时的逻辑,如果确实是这样的话。

那么如何正确地检查一个com对象是否为空?还有其他地方我想做检查,比如在试图关闭工作簿和退出Excel时,在catch块内。但是,我现在担心检查catch块内的excel object == null是否会产生一个不可见的包装器,所以我使用了嵌套的try..catch块。但那感觉也不对。

问题是,发生了什么,什么是检查的最佳做法,如果你的Excel com对象实例化正确,如果我的方法是不正确的

为什么Excel com对象不从内存中释放,如果我检查它为空

尝试将垃圾收集器调用移动到包装器方法。这样,当您调用它时,您可以确保所有自动创建的引用都在作用域之外。

private void CreateWorkbookWithCleanup(...)
{
    CreateWorkbook(...);
    // Yes, we really want to call those two methods twice to make sure all
    // COM objects AND all RCWs are collected.
    GC.Collect();
    GC.WaitForPendingFinalizers();
    GC.Collect();
    GC.WaitForPendingFinalizers();
}

同样,根据我的经验,你不需要调用Marshal.ReleaseComObject,如果你释放了所有的引用,所有的东西都被垃圾收集器释放了。也不需要睡觉。