Cannot close Excel.exe after Interop process

梦想的初衷 提交于 2019-12-17 04:29:47

问题


I'm having an issue with Excel Interop.

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

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.RealeaseComObject it should be work, but nothing. How can I fix this?

Thank you.


回答1:


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

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

...because in this way you create RCW objects 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).

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);



回答2:


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.

    /// <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();
    }



回答3:


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();
}



回答4:


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 Workbooks that you do not hold a reference to.

Even references like:

targetRange.Columns.AutoFit()

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

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:

    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.




回答5:


In your code you have:

excel.Workbooks.Open(...)

excel.Workbooks is creating a COM object. You are then calling the Open function from that COM object. You are not, however, releasing the COM object when you have finished.

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.

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

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




回答6:


As stated in other answers, using two dots will 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.

I use a generic IDisposable wrapper class which 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).

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();
            }
        }
    }
}



回答7:


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

First, import SendMessage function:

[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:

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



回答8:


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".

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.




回答9:


Cannot close Excel.exe after Interop process

Don't make this too complicated!! Just create a simple method and call that method as follows :

// to kill the EXCELsheet file process from process Bar
private void KillSpecificExcelFileProcess() {
  foreach (Process clsProcess in Process.GetProcesses())
    if (clsProcess.ProcessName.Equals("EXCEL"))  //Process Excel?
      clsProcess.Kill();
    }



回答10:


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.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);
        }



回答11:


@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.

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

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

  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.

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().

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.



来源:https://stackoverflow.com/questions/17367411/cannot-close-excel-exe-after-interop-process

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!