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

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

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

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


当前回答

我有一个想法,尝试关闭你打开的excel进程:

在打开exclapplication之前,获取名为oldProcessIds的所有进程ID。打开重叠部分。现在获取名为nowProcessIds的所有超应用进程ID。当需要退出时,杀死oldProcessIds和nowProcessIds之间的except id。私有静态Excel.Application GetExcelApp(){如果(_excelApp==空){var processIds=System.Diagnostics.Process.GetProcessesByName(“EXCEL”).Select(a=>a.Id).ToList();_excelApp=新建Excel.Application();_excelApp.DisplayAlerts=false;_excelApp.Visible=false;_excelApp.ScreenUpdate=false;var newProcessIds=System.Diagnostics.Process.GetProcessesByName(“EXCEL”).Select(a=>a.Id).ToList();_excelApplicationProcessId=newProcessIds.Except(processIds).FirstOrDefault();}return _excelApp;}公共静态void Dispose(){尝试{_excelApp.Workbooks.Close();_excelApp.Quit();System.Runtime.InteropServices.Marsal.ReleaseComObject(_excelApp);_excelApp=空;GC.Collect();GC.WaitForPendingFinalizers();if(_excelApplicationProcessId!=默认值(int)){var process=System.Diagnostics.process.GetProcessById(_excelApplicationProcessId);过程Kill();_excelApplicationProcessId=默认值(int);}}catch(异常ex){_excelApp=空;}}

其他回答

在其他答案中考虑的三种一般策略中,杀死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。

我目前正在研究Office自动化,并偶然发现了一个每次都适用于我的解决方案。它很简单,不涉及杀死任何进程。

似乎只要在当前的活动进程中循环,并以任何方式“访问”一个开放的Excel进程,Excel的任何游离挂起实例都将被删除。下面的代码只是检查名称为“Excel”的进程,然后将进程的MainWindowTitle属性写入字符串。与进程的这种“交互”似乎使Windows赶上并中止了冻结的Excel实例。

我在开发退出的加载项之前运行下面的方法,因为它会触发卸载事件。它每次都会删除所有挂起的Excel实例。老实说,我不完全确定为什么这样做,但它对我来说很好,可以放在任何Excel应用程序的末尾,而不必担心双点、Marshal.ReleaseComObject或杀死进程。我很想知道为什么这是有效的。

public static void SweepExcelProcesses()
{           
            if (Process.GetProcessesByName("EXCEL").Length != 0)
            {
                Process[] processes = Process.GetProcesses();
                foreach (Process process in processes)
                {
                    if (process.ProcessName.ToString() == "excel")
                    {                           
                        string title = process.MainWindowTitle;
                    }
                }
            }
}

为了说明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

我有一个想法,尝试关闭你打开的excel进程:

在打开exclapplication之前,获取名为oldProcessIds的所有进程ID。打开重叠部分。现在获取名为nowProcessIds的所有超应用进程ID。当需要退出时,杀死oldProcessIds和nowProcessIds之间的except id。私有静态Excel.Application GetExcelApp(){如果(_excelApp==空){var processIds=System.Diagnostics.Process.GetProcessesByName(“EXCEL”).Select(a=>a.Id).ToList();_excelApp=新建Excel.Application();_excelApp.DisplayAlerts=false;_excelApp.Visible=false;_excelApp.ScreenUpdate=false;var newProcessIds=System.Diagnostics.Process.GetProcessesByName(“EXCEL”).Select(a=>a.Id).ToList();_excelApplicationProcessId=newProcessIds.Except(processIds).FirstOrDefault();}return _excelApp;}公共静态void Dispose(){尝试{_excelApp.Workbooks.Close();_excelApp.Quit();System.Runtime.InteropServices.Marsal.ReleaseComObject(_excelApp);_excelApp=空;GC.Collect();GC.WaitForPendingFinalizers();if(_excelApplicationProcessId!=默认值(int)){var process=System.Diagnostics.process.GetProcessById(_excelApplicationProcessId);过程Kill();_excelApplicationProcessId=默认值(int);}}catch(异常ex){_excelApp=空;}}

当上面的所有内容都不起作用时,请尝试给Excel一些时间来关闭工作表:

app.workbooks.Close();
Thread.Sleep(500); // adjust, for me it works at around 300+
app.Quit();

...
FinalReleaseComObject(app);