C# 互操作过程后无法关闭 Excel.exe

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/17367411/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-10 09:19:22  来源:igfitidea点击:

Cannot close Excel.exe after Interop process

c#winformsexcel-interop

提问by proven?al le breton

I'm having an issue with Excel Interop.

我遇到了 Excel Interop 的问题。

The Excel.exe doesn't close even if when I realease instances.

即使我释放实例,Excel.exe 也不会关闭。

Here is my code :

这是我的代码:

using xl = Microsoft.Office.Interop.Excel;


xl.Application excel = new xl.Application();
excel.Visible = true;
excel.ScreenUpdating = false;
if (wordFile.Contains(".csv") || wordFile.Contains(".xls"))
{
   //typeExcel become a string of the document name
   string typeExcel = wordFile.ToString();
   xl.Workbook workbook = excel.Workbooks.Open(typeExcel,
                                                oMissing,  oMissing,  oMissing,  oMissing,
                                                oMissing,  oMissing,  oMissing,  oMissing,
                                                oMissing,  oMissing,  oMissing,  oMissing,
                                                oMissing,  oMissing);
   object outputFileName = null;
   if (wordFile.Contains(".xls"))
   {
     outputFileName = wordFile.Replace(".xls", ".pdf");
   }
   else if (wordFile.Contains(".csv"))
   {
     outputFileName = wordFile.Replace(".csv", ".pdf");
   }

   workbook.ExportAsFixedFormat(XlFixedFormatType.xlTypePDF, outputFileName, 
                                 XlFixedFormatQuality.xlQualityStandard, oMissing,
                                 oMissing, oMissing, oMissing, oMissing, oMissing);

   object saveChanges = xl.XlSaveAction.xlDoNotSaveChanges;
   ((xl._Workbook)workbook).Close(saveChanges, oMissing, oMissing);

   Marshal.ReleaseComObject(workbook);
   workbook = null;
}

I saw that, with the Marshal.RealeaseComObjectit should be work, but nothing. How can I fix this?

我看到了,Marshal.RealeaseComObject它应该可以工作,但什么也没有。我怎样才能解决这个问题?

Thank you.

谢谢你。

采纳答案by Dzmitry Martavoi

Simple rule: avoid using double-dot-calling expressions, such as this:

简单规则:避免使用双点调用表达式,例如:

var workbook = excel.Workbooks.Open(/*params*/)

...because in this way you create RCWobjects not only for workbook, but for Workbooks, and you should release it too (which is not possible if a reference to the object is not maintained).

...因为通过这种方式,您不仅可以为,而且可以为创建RCW对象,并且您也应该释放它(如果不维护对对象的引用,这是不可能的)。workbookWorkbooks

So, the right way will be:

所以,正确的方法是:

var workbooks = excel.Workbooks;
var workbook = workbooks.Open(/*params*/)

//business logic here

Marshal.ReleaseComObject(workbook);
Marshal.ReleaseComObject(workbooks);
Marshal.ReleaseComObject(excel);

回答by JDB still remembers Monica

In your code you have:

在您的代码中,您有:

excel.Workbooks.Open(...)

excel.Workbooksis creating a COM object. You are then calling the Openfunction from that COM object. You are not, however, releasing the COM object when you have finished.

excel.Workbooks正在创建一个 COM 对象。然后,您Open将从该 COM 对象调用该函数。但是,完成后您并没有释放 COM 对象。

This is a common issue when dealing with COM objects. Basically, you should never have more than one dot in your expression because you will need to clean up the COM objects when you've finished.

这是处理 COM 对象时的常见问题。基本上,表达式中的点永远不应超过一个,因为完成后您将需要清理 COM 对象。

The topic is simply too big to explore completely in an answer, but I think you'll find Jake Ginnivan's article on the subject extremely helpful: VSTO and COM Interop

该主题太大而无法在答案中完全探索,但我认为您会发现 Jake Ginnivan 关于该主题的文章非常有帮助:VSTO and COM Interop

If you get tired of all those ReleaseComObject calls, you may find this question helpful:
How to properly clean up Excel interop object in C#, 2012 edition

如果您厌倦了所有这些 ReleaseComObject 调用,您可能会发现这个问题很有帮助:
如何正确清理 C# 中的 Excel 互操作对象,2012 版

回答by qJake

Here is a snippet of code I wrote, because I had the same problem as you. Basically, you need to close the workbook, quit the application, and then release ALL of your COM objects (not just the Excel Application object). Finally, call the garbage collector for good measure.

这是我写的一段代码,因为我和你有同样的问题。基本上,您需要关闭工作簿,退出应用程序,然后释放所有 COM 对象(不仅仅是 Excel 应用程序对象)。最后,调用垃圾收集器以获得良好的测量。

    /// <summary>
    /// Disposes the current <see cref="ExcelGraph" /> object and cleans up any resources.
    /// </summary>
    public void Dispose()
    {
        // Cleanup
        xWorkbook.Close(false);
        xApp.Quit();

        // Manual disposal because of COM
        while (Marshal.ReleaseComObject(xApp) != 0) { }
        while (Marshal.ReleaseComObject(xWorkbook) != 0) { }
        while (Marshal.ReleaseComObject(xWorksheets) != 0) { }
        while (Marshal.ReleaseComObject(xWorksheet) != 0) { }
        while (Marshal.ReleaseComObject(xCharts) != 0) { }
        while (Marshal.ReleaseComObject(xMyChart) != 0) { }
        while (Marshal.ReleaseComObject(xGraph) != 0) { }
        while (Marshal.ReleaseComObject(xSeriesColl) != 0) { }
        while (Marshal.ReleaseComObject(xSeries) != 0) { }
        xApp = null;
        xWorkbook = null;
        xWorksheets = null;
        xWorksheet = null;
        xCharts = null;
        xMyChart = null;
        xGraph = null;
        xSeriesColl = null;
        xSeries = null;

        GC.Collect();
        GC.WaitForPendingFinalizers();
    }

回答by Lawrence Thurman

Rules - never use no more that one dot

规则 - 永远不要使用超过一个点

-- one dot

——一个点

var range = ((Range)xlWorksheet.Cells[rowIndex, setColumn]);
var hyperLinks = range.Hyperlinks;
hyperLinks.Add(range, data);

-- Two or more dots

-- 两个或更多点

 (Range)xlWorksheet.Cells[rowIndex, setColumn]).Hyperlinks.Add(range, data);

-- Example

- 例子

 using Microsoft.Office.Interop.Excel;

 Application xls = null;
 Workbooks workBooks = null;
 Workbook workBook = null;
 Sheets sheets = null;
 Worksheet workSheet1 = null;
 Worksheet workSheet2 = null;

 workBooks = xls.Workbooks;
 workBook = workBooks.Open(workSpaceFile);
 sheets = workBook.Worksheets;
 workSheet1 = (Worksheet)sheets[1];


// removing from Memory
 if (xls != null)
 {    
   foreach (Microsoft.Office.Interop.Excel.Worksheet sheet in sheets)
   {
      ReleaseObject(sheet);
   }

   ReleaseObject(sheets);
   workBook.Close();
   ReleaseObject(workBook);
   ReleaseObject(workBooks);

   xls.Application.Quit(); // THIS IS WHAT IS CAUSES EXCEL TO CLOSE
   xls.Quit();
   ReleaseObject(xls);

   sheets = null;
   workBook = null;
   workBooks = null;
   xls = null;

   GC.Collect();
   GC.WaitForPendingFinalizers();
   GC.Collect();
   GC.WaitForPendingFinalizers();
}

回答by flodis

It is tricky to get rid of all references since you have to guess if calls like:

摆脱所有引用是很棘手的,因为您必须猜测调用是否如下:

var workbook = excel.Workbooks.Open("")

Creates an instance of Workbooksthat you do not hold a reference to.

创建一个Workbooks您没有引用的实例。

Even references like:

甚至像这样的引用:

targetRange.Columns.AutoFit()

Will create an instance of .Columns()without you knowing and not released properly.

.Columns()在你不知情的情况下创建一个实例并且没有正确释放。

I ended up writing a class holding a list of object references that could dispose all objects in reverse order.

我最终编写了一个包含对象引用列表的类,这些对象引用可以以相反的顺序处理所有对象。

The class has a list of objects and Add()functions for anything you reference as you use Excel interop that returns the object itself:

Add()当您使用返回对象本身的 Excel 互操作时,该类具有您引用的任何对象和函数的对象和函数列表:

    public List<Object> _interopObjectList = new List<Object>();

    public Excel.Application add(Excel.Application obj)
    {
        _interopObjectList.Add(obj);
        return obj;
    }

    public Excel.Range add(Excel.Range obj)
    {
        _interopObjectList.Add(obj);
        return obj;
    }

    public Excel.Workbook add(Excel.Workbook obj)
    {
        _interopObjectList.Add(obj);
        return obj;
    }

    public Excel.Worksheet add(Excel.Worksheet obj)
    {
        _interopObjectList.Add(obj);
        return obj;
    }

    public Excel.Worksheets add(Excel.Worksheets obj)
    {
        _interopObjectList.Add(obj);
        return obj;
    }

    public Excel.Sheets add(Excel.Sheets obj)
    {
        _interopObjectList.Add(obj);
        return obj;
    }


    public Excel.Workbooks add(Excel.Workbooks obj)
    {
        _interopObjectList.Add(obj);
        return obj;
    }

Then to unregister objects I used the following code:

然后为了取消注册对象,我使用了以下代码:

    //Release all registered interop objects in reverse order
    public void unregister()
    {
        //Loop object list in reverse order and release Office object
        for (int i=_interopObjectList.Count-1; i>=0 ; i -= 1)
        { ReleaseComObject(_interopObjectList[i]); }

        //Clear object list
        _interopObjectList.Clear();
    }


    /// <summary>
    /// Release a com interop object 
    /// </summary>
    /// <param name="obj"></param>
     public static void ReleaseComObject(object obj)
     {
         if (obj != null && InteropServices.Marshal.IsComObject(obj))
             try
             {
                 InteropServices.Marshal.FinalReleaseComObject(obj);
             }
             catch { }
             finally
             {
                 obj = null;
             }

         GC.Collect();
         GC.WaitForPendingFinalizers();
         GC.Collect();
         GC.WaitForPendingFinalizers();
     }

Then principle is to create the class and capture references like this:

然后原理是创建类并捕获这样的引用:

//Create helper class
xlsHandler xlObj = new xlsHandler();

..

//Sample - Capture reference to excel application
Excel.Application _excelApp = xlObj.add(new Excel.Application());

..
//Sample - Call .Autofit() on a cell range and capture reference to .Columns() 
xlObj.add(_targetCell.Columns).AutoFit();

..

//Release all objects collected by helper class
xlObj.unregister();

Not perhaps code of great beauty but may inspire to something useful.

也许不是非常美丽的代码,但可能会激发一些有用的东西。

回答by Denis Molodtsov

In case you are desperate. Do not use this approach unless you understand what it does:

万一你绝望了除非您了解它的作用,否则不要使用这种方法

foreach (Process proc in System.Diagnostics.Process.GetProcessesByName("EXCEL"))
{
  proc.Kill();
}

Note: This kill every process named "EXCEL".

注意:这会杀死每个名为“EXCEL”的进程。

I had to do it becase even though I've closed every single COM object in my code I still had stubborn Excel.exe process just hanging there. This is by no means the best solution, of course.

我不得不这样做,因为即使我关闭了代码中的每个 COM 对象,我仍然有顽固的 Excel.exe 进程只是挂在那里。当然,这绝不是最好的解决方案。

回答by Kevin Vuilleumier

Alternatively, you can kill the Excel process as explained here.

或者,您可以按照此处的说明终止Excel 进程。

First, import SendMessagefunction:

首先,导入SendMessage函数:

[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);

Then, send the WM_CLOSE message to the main window:

然后,向主窗口发送 WM_CLOSE 消息:

SendMessage((IntPtr)excel.Hwnd, 0x10, IntPtr.Zero, IntPtr.Zero);

回答by Heliac

As stated in other answers, using two dotswill create hidden references that cannot be closed by Marshal.FinalReleaseComObject. I just wanted to share my solution, which eliminates the need to remember Marshal.FinalReleaseComObject- it's really easy to miss, and a pain to locate the culprit.

正如其他答案中所述,使用两个点将创建无法由Marshal.FinalReleaseComObject. 我只是想分享我的解决方案,它消除了记忆的需要Marshal.FinalReleaseComObject——它真的很容易错过,而且很难找到罪魁祸首。

I use a generic IDisposable wrapper classwhich can be used on any COM object. It works like a charm, and it keeps everything nice and clean. I can even reuse private fields (e.g. this.worksheet). It also auto-releases the object when something throws an error, due to the nature of IDisposable (the Dispose method runs as a finally).

我使用了一个通用的 IDisposable 包装类,它可以用于任何 COM 对象。它的作用就像一个魅力,它让一切都保持干净整洁。我什至可以重用私有字段(例如this.worksheet)。由于 IDisposable 的性质(Dispose 方法作为 运行finally),它还会在出现错误时自动释放对象。

using Microsoft.Office.Interop.Excel;

public class ExcelService
{
    private _Worksheet worksheet;

    private class ComObject<TType> : IDisposable
    {
        public TType Instance { get; set; }

        public ComObject(TType instance)
        {
            this.Instance = instance;
        }

        public void Dispose()
        {
            System.Runtime.InteropServices.Marshal.FinalReleaseComObject(this.Instance);
        }
    }

    public void CreateExcelFile(string fullFilePath)
    {
        using (var comApplication = new ComObject<Application>(new Application()))
        {
            var excelInstance = comApplication.Instance;
            excelInstance.Visible = false;
            excelInstance.DisplayAlerts = false;

            try
            {
                using (var workbooks = new ComObject<Workbooks>(excelInstance.Workbooks))
                using (var workbook = new ComObject<_Workbook>(workbooks.Instance.Add()))
                using (var comSheets = new ComObject<Sheets>(workbook.Instance.Sheets))
                {
                    using (var comSheet = new ComObject<_Worksheet>(comSheets.Instance["Sheet1"]))
                    {
                        this.worksheet = comSheet.Instance;
                        this.worksheet.Name = "Action";
                        this.worksheet.Visible = XlSheetVisibility.xlSheetHidden;
                    }

                    using (var comSheet = new ComObject<_Worksheet>(comSheets.Instance["Sheet2"]))
                    {
                        this.worksheet = comSheet.Instance;
                        this.worksheet.Name = "Status";
                        this.worksheet.Visible = XlSheetVisibility.xlSheetHidden;
                    }

                    using (var comSheet = new ComObject<_Worksheet>(comSheets.Instance["Sheet3"]))
                    {
                        this.worksheet = comSheet.Instance;
                        this.worksheet.Name = "ItemPrices";
                        this.worksheet.Activate();

                        using (var comRange = new ComObject<Range>(this.worksheet.Range["A4"]))
                        using (var comWindow = new ComObject<Window>(excelInstance.ActiveWindow))
                        {
                            comRange.Instance.Select();
                            comWindow.Instance.FreezePanes = true;
                        }
                    }

                    if (this.fullFilePath != null)
                    {
                        var currentWorkbook = (workbook.Instance as _Workbook);
                        currentWorkbook.SaveAs(this.fullFilePath, XlFileFormat.xlWorkbookNormal);
                        currentWorkbook.Close(false);
                    }
                }
            }
            catch (Exception ex)
            {
                System.Diagnostics.Trace.WriteLine(ex.Message);
                throw;
            }
            finally
            {
                // Close Excel instance
                excelInstance.Quit();
            }
        }
    }
}

回答by kki

I had same issue , we can solve the issue without any killing, we always forget to close interfaces which we have used form Microsoft.Office.Interop.Excel class so here is the code snippet and follow the structure and way have cleared objects , also keep an eye on Sheets interface in your code this is the main culprit we often close the application,Workbook,workbooks,range,sheet but we forget or unknowingly dont release the Sheets object or used interface so here is the code :

我有同样的问题,我们可以解决问题而没有任何杀戮,我们总是忘记关闭我们从Microsoft.Office.Interop.Excel类中使用的接口所以这里是代码片段并按照结构和方式清除对象,也注意代码中的 Sheets 界面,这是我们经常关闭应用程序、工作簿、工作簿、范围、工作表的主要罪魁祸首,但我们忘记或不知不觉地不释放 Sheets 对象或使用的界面,所以这里是代码:

               Microsoft.Office.Interop.Excel.Application app = null;
        Microsoft.Office.Interop.Excel.Workbooks books = null;
        Workbook book = null;
        Sheets sheets = null;
        Worksheet sheet = null;
        Range range = null;

        try
        {
            app = new Microsoft.Office.Interop.Excel.Application();
            books = app.Workbooks;
            book = books.Add();
            sheets = book.Sheets;
            sheet = sheets.Add();
            range = sheet.Range["A1"];
            range.Value = "Lorem Ipsum";
            book.SaveAs(@"C:\Temp\ExcelBook" + DateTime.Now.Millisecond + ".xlsx");
            book.Close();
            app.Quit();
        }
        finally
        {
            if (range != null) Marshal.ReleaseComObject(range);
            if (sheet != null) Marshal.ReleaseComObject(sheet);
            if (sheets != null) Marshal.ReleaseComObject(sheets);
            if (book != null) Marshal.ReleaseComObject(book);
            if (books != null) Marshal.ReleaseComObject(books);
            if (app != null) Marshal.ReleaseComObject(app);
        }

回答by jimhark

@Denis Molodtsov in an attempt to be helpful suggested killing all processes named 'EXCEL'. That seems to be asking for trouble. There are already many answers that describe ways to get the process to stop after the call to excel.quit() by playing nice with COM interop. This is best if you can make it work.

@Denis Molodtsov 试图提供帮助建议杀死所有名为“EXCEL”的进程。这似乎是在自找麻烦。已经有很多答案描述了在调用 excel.quit() 后通过与 COM 互操作配合良好来停止进程的方法。这是最好的,如果你能让它工作。

@Kevin Vuilleumier had a great suggestion to send WM_CLOSE to the Excel window. I plan to test this.

@Kevin Vuilleumier 有一个很好的建议,将 WM_CLOSE 发送到 Excel 窗口。我打算测试这个。

If for some reason you need to kill an Excel App Object's Excel process, you can target it specifically using something like this:

如果出于某种原因您需要终止 Excel App Object 的 Excel 进程,您可以使用以下内容专门针对它:

  using System.Diagnostics;
  using System.Runtime.InteropServices;

// . . .

    [DllImport("user32.dll", SetLastError=true)]
    public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId);

// . . .

    uint excelAppPid;
    uint tid = GetWindowThreadProcessId(excel.Hwnd, out excelAppPid);

    if (tid)
    {
      Process excelAppProc = Process.GetProcessById($excelPid)
      if (excelAppProc)
      {
        excelAppProc.Kill()
      }
    }

I don't have time to fully test in C#, but I ran a quick test in Powershell where I'm having a problem with Excel not terminating and this approach works.

我没有时间在 C# 中进行全面测试,但我在 Powershell 中运行了一个快速测试,在那里我遇到了 Excel 未终止的问题,这种方法有效。

It's pretty straightforward. Excel App object's Hwnd property is the Excel process's hidden window handle. Pass excel.Hwnd to GetWindowThreadProcessId to get the process ID. Use that to open the process, finally invoke Kill().

这很简单。Excel App 对象的 Hwnd 属性是 Excel 进程的隐藏窗口句柄。将 excel.Hwnd 传递给 GetWindowThreadProcessId 以获取进程 ID。使用它打开进程,最后调用Kill()。

At least we're sure we're killing the right process. Well, pretty sure. If the Excel process already terminated normally, it's process ID could be reused by a new process. To limit this possibility, it's important not to wait between calling excel.quit() and attempting to kill.

至少我们确信我们正在杀死正确的过程。嗯,很确定。如果 Excel 进程已经正常终止,它的进程 ID 可以被新进程重用。为了限制这种可能性,重要的是不要在调用 excel.quit() 和尝试杀死之间等待。