Excel进程在使用interop后仍然保持开启状态;传统方法无效。

9

我在调试代码时遇到了一些问题。使用Excel互操作从工作簿中提取一些值;但是,程序退出后Excel仍然保持打开状态。我尝试了传统的解决方法,但它仍然会在运行代码的所有计算机上保留对Excel的引用。

private void TestExcel()
    {
        Excel.Application excel = new Excel.Application();
        Excel.Workbooks books = excel.Workbooks;
        Excel.Workbook book = books.Open("C:\\test.xlsm");

        book.Close();
        books.Close();
        excel.Quit();

        Marshal.ReleaseComObject(book);
        Marshal.ReleaseComObject(books);
        Marshal.ReleaseComObject(excel);
    }

即使是这个简单的代码片段也可以处理多个文件(xlsm,xlsx,xls)。目前我们已经有一个解决方法来关闭我们打开的Excel进程,但为了保持自己的理智,我更想让它正常工作。
我应该补充一点,即我已经将问题缩小到了“Workbook”变量。如果我删除对“books.Open()”的调用和所有对“book”的引用,则它可以成功关闭。

你的代码在我测试时是可行的,可能你在运行时遇到了一些异常引起了问题? - msmucker0527
4个回答

14

这对我来说已经成功了:

        xlApp.Quit();

        //release all memory - stop EXCEL.exe from hanging around.
        if (xlWorkBook != null) { Marshal.ReleaseComObject(xlWorkBook); } //release each workbook like this
        if (xlWorkSheet != null) { Marshal.ReleaseComObject(xlWorkSheet); } //release each worksheet like this
        if (xlApp != null) { Marshal.ReleaseComObject(xlApp); } //release the Excel application
        xlWorkBook = null; //set each memory reference to null.
        xlWorkSheet = null;
        xlApp = null;
        GC.Collect();

2
标记为答案,因为这是正确的。虽然在我的开发机器上仍然无法工作,但在干净的安装或我的家用电脑上可以工作。奇怪。 - bradenb

5
这段代码对我有效。
//Declare separate object variables
Excel.Application xlApp = new Excel.Application();
Excel.Workbooks xlWorkbooks = xlApp.Workbooks;
Excel.Workbook xlWorkbook = xlWorkbooks.Add(Missing.Value);
Excel.Worksheet xlWorksheet = (Excel.Worksheet)xlWorkbook.Worksheets.get_Item(1);

//Create worksheet

xlWorkbook.Close(false, Missing.Value, Missing.Value);
xlWorkbooks.Close();
xlApp.Quit();

Marshal.FinalReleaseComObject(xlWorksheet);
Marshal.FinalReleaseComObject(xlWorkbook);
Marshal.FinalReleaseComObject(xlWorkbooks);
Marshal.FinalReleaseComObject(xlApp);

xlWorksheet = null;
xlWorkbook = null;
xlWorkbooks = null;
xlApp = null;

GC.Collect();

这篇来自微软的文章提供了关于这个问题的一些好的信息:

This article from Microsoft


3
这是我解决这个问题的方法:
// Store the Excel processes before opening.
Process[] processesBefore = Process.GetProcessesByName("excel");

// Open the file in Excel.
Application excelApplication = new Application();
Workbook excelWorkbook = excelApplication.Workbooks.Open(Filename);

// Get Excel processes after opening the file.
Process[] processesAfter = Process.GetProcessesByName("excel");

// Now find the process id that was created, and store it.
int processID = 0;
foreach (Process process in processesAfter)
{
    if (!processesBefore.Select(p => p.Id).Contains(process.Id))
    {
        processID = process.Id;
    }
}

// Do the Excel stuff

// Now close the file with the COM object.
excelWorkbook.Close();
excelApplication.Workbooks.Close();
excelApplication.Quit();

// And now kill the process.
if (processID != 0)
{
    Process process = Process.GetProcessById(processID);
    process.Kill();
}

3

我完全是一个COM的菜鸟,在很长时间以前的一个项目中只用过一次,但这里有一段我当时使用的代码片段。我可能在网上找到它,不记得了。无论如何,我在此将其完整地粘贴出来;)

public static class ComBlackBox
{
    public static void ReleaseObject(object obj)
    {
        try
        {
            System.Runtime.InteropServices.Marshal.ReleaseComObject(obj);
            obj = null;
        }
        catch (ArgumentException ex)
        {
            obj = null;
            MessageBox.Show("Unable to release the Object " + ex.Message);
        }
        finally
        {
            GC.Collect();
        }
    } 
}

我现在无法尝试它,但它可能有效(我真的不记得任何细节)。也许它会帮助你解决问题。如果这段代码有明显的问题,请随时指出,我真的很菜。


1
这是部分工作,因为你在最后的finally try catch中使用了GC.Collect()。然而,它直到进入finally部分才真正关闭Excel。为什么?因为对象是按值传递的,如果你将sheet/sheets/workbook或excel传递到函数中,它们的COM值将保持不变,无论你对对象本身做什么。除非你可以通过引用传递这些COM对象,但我尝试过,它不允许我这样做。 - Hoang Minh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接