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

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

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

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


当前回答

Excel不会退出,因为应用程序仍保留对COM对象的引用。

我猜您调用了COM对象的至少一个成员,而没有将其分配给变量。

对我来说,它是excelApp.Worksheets对象,我直接使用它,而没有将它分配给变量:

Worksheet sheet = excelApp.Worksheets.Open(...);
...
Marshal.ReleaseComObject(sheet);

我不知道C#在内部为WorksheetsCOM对象创建了一个包装器,但我的代码没有发布它(因为我不知道),这也是Excel没有卸载的原因。

我在这个页面上找到了我的问题的解决方案,它也有一个关于C#中COM对象用法的好规则:

切勿对COM对象使用两点。


因此,有了这些知识,正确的方法是:

Worksheets sheets = excelApp.Worksheets; // <-- The important part
Worksheet sheet = sheets.Open(...);
...
Marshal.ReleaseComObject(sheets);
Marshal.ReleaseComObject(sheet);

尸检后更新:

我希望每一位读者都能非常仔细地阅读Hans Passant的回答,因为它解释了我和许多其他开发人员偶然遇到的陷阱。几年前我写这个答案时,我不知道调试器对垃圾收集器的影响,得出了错误的结论。为了历史起见,我的答案保持不变,但请阅读此链接,不要走“两点”的道路:了解.NET中的垃圾收集和使用IDisposable清理Excel互操作对象

其他回答

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

好的。让no挑战并开始销毁它,但考虑不要销毁并行运行的其他Excel实例。

因此,获取当前处理器的列表并获取EXCEL进程的PID,然后一旦完成任务,我们将在进程列表中创建一个具有唯一PID的新访客,找到并销毁该访客。

<请记住,在excel工作过程中,任何新的excel流程都将被检测为新的并被销毁><更好的解决方案是捕获新创建的excel对象的PID并销毁它>

Process[] prs = Process.GetProcesses();
List<int> excelPID = new List<int>();
foreach (Process p in prs)
   if (p.ProcessName == "EXCEL")
       excelPID.Add(p.Id);

.... // your job 

prs = Process.GetProcesses();
foreach (Process p in prs)
   if (p.ProcessName == "EXCEL" && !excelPID.Contains(p.Id))
       p.Kill();

这解决了我的问题,也希望你的问题。

拍摄Excel程序并咀嚼泡泡糖

public class MyExcelInteropClass
{
    Excel.Application xlApp;
    Excel.Workbook xlBook;

    public void dothingswithExcel() 
    {
        try { /* Do stuff manipulating cells sheets and workbooks ... */ }
        catch {}
        finally {KillExcelProcess(xlApp);}
    }

    static void KillExcelProcess(Excel.Application xlApp)
    {
        if (xlApp != null)
        {
            int excelProcessId = 0;
            GetWindowThreadProcessId(xlApp.Hwnd, out excelProcessId);
            Process p = Process.GetProcessById(excelProcessId);
            p.Kill();
            xlApp = null;
        }
    }

    [DllImport("user32.dll")]
    static extern int GetWindowThreadProcessId(int hWnd, out int lpdwProcessId);
}

使用Microsoft Excel 2016测试

一个真正经过测试的解决方案。

参考C#,请参见:https://stackoverflow.com/a/1307180/10442623

VB.net参考请参见:https://stackoverflow.com/a/54044646/10442623

1包括课堂作业

2实现该类以处理excel进程的适当处理

我的解决方案

[DllImport("user32.dll")]
static extern int GetWindowThreadProcessId(int hWnd, out int lpdwProcessId);

private void GenerateExcel()
{
    var excel = new Microsoft.Office.Interop.Excel.Application();
    int id;
    // Find the Excel Process Id (ath the end, you kill him
    GetWindowThreadProcessId(excel.Hwnd, out id);
    Process excelProcess = Process.GetProcessById(id);

try
{
    // Your code
}
finally
{
    excel.Quit();

    // Kill him !
    excelProcess.Kill();
}