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

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

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

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


当前回答

我目前正在研究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命名空间中的任何内容都需要发布。时期

你不能这样做:

Worksheet ws = excel.WorkBooks[1].WorkSheets[1];

你必须这样做

Workbooks books = excel.WorkBooks;
Workbook book = books[1];
Sheets sheets = book.WorkSheets;
Worksheet ws = sheets[1];

随后释放物体。

普通开发人员,你的解决方案都不适合我,所以我决定实施一个新的技巧。

首先,让我们指定“我们的目标是什么?”=>“在任务管理器中完成任务后不要看到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();

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

我一直遵循VVS答案中的建议。然而,为了使这个答案与最新的选项保持同步,我认为我未来的所有项目都将使用“NetOffice”库。

NetOffice是Office PIA的完全替代品,完全不受版本限制。它是一个托管COM包装器的集合,可以处理在.NET中使用Microsoft Office时经常引起此类麻烦的清理。

一些关键功能包括:

大部分与版本无关(记录了与版本相关的功能)无相关性无PIA没有注册无VSTO

我与该项目毫无关联;我真的很欣赏头痛的明显减轻。

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

到目前为止,似乎所有的答案都涉及其中一些:

终止进程使用GC.Collect()跟踪每个COM对象并正确释放它。

这让我意识到这个问题有多么困难:)

我一直在开发一个库来简化对Excel的访问,我正在努力确保使用它的人不会留下一片混乱(手指交叉)。

我没有直接在Interop提供的接口上进行编写,而是使用扩展方法来简化工作。类似于ApplicationHelpers.CreateExcel()或工作簿.CreateWorksheet(“mySheetNameThatWillBeValidated”)。自然,任何创建的东西都可能会在以后的清理中导致问题,所以我实际上更倾向于在最后的手段中终止这个过程。然而,正确清理(第三种选择)可能是破坏性最小、控制性最强的。

因此,在这种情况下,我想知道这样做是否不是最好的:

public abstract class ReleaseContainer<T>
{
    private readonly Action<T> actionOnT;

    protected ReleaseContainer(T releasible, Action<T> actionOnT)
    {
        this.actionOnT = actionOnT;
        this.Releasible = releasible;
    }

    ~ReleaseContainer()
    {
        Release();
    }

    public T Releasible { get; private set; }

    private void Release()
    {
        actionOnT(Releasible);
        Releasible = default(T);
    }
}

我用“不可行”来避免与一次性使用混淆。但将其扩展到IDisposable应该很容易。

这样的实现:

public class ApplicationContainer : ReleaseContainer<Application>
{
    public ApplicationContainer()
        : base(new Application(), ActionOnExcel)
    {
    }

    private static void ActionOnExcel(Application application)
    {
        application.Show(); // extension method. want to make sure the app is visible.
        application.Quit();
        Marshal.FinalReleaseComObject(application);
    }
}

可以对所有类型的COM对象执行类似的操作。

在工厂方法中:

    public static Application CreateExcelApplication(bool hidden = false)
    {
        var excel = new ApplicationContainer().Releasible;
        excel.Visible = !hidden;

        return excel;
    }

我希望每个容器都会被GC正确地销毁,因此会自动调用Quit和Marshal.FinalReleaseComObject。

评论?或者这是对第三类问题的回答?