Interop进程后无法关闭Excel.exe

问题描述 投票:27回答:11

我遇到了Excel Interop的问题。

即使我发布实例,Excel.exe也不会关闭。

这是我的代码:

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

我看到,用Marshal.RealeaseComObject它应该是工作,但没有。我怎样才能解决这个问题?

谢谢。

c# winforms excel-interop
11个回答
64
投票

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

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

...因为这样你不仅为RCW创建workbook对象,而且为Workbooks创建var workbooks = excel.Workbooks; var workbook = workbooks.Open(/*params*/) //business logic here Marshal.ReleaseComObject(workbook); Marshal.ReleaseComObject(workbooks); Marshal.ReleaseComObject(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();
    }

0
投票

我遇到了同样的问题,我们可以解决这个问题,没有任何查杀,我们总是忘记关闭我们使用过的Microsoft.Office.Interop.Excel类接口,所以这里是代码片段并按照结构和方式清除了对象,密切注意你的代码中的Sheets接口这是我们经常关闭应用程序,工作簿,工作簿,范围,工作表的主要罪魁祸首但是我们忘记或不知道不要发布Sheets对象或使用的接口,所以这里是代码:

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

0
投票

@Denis Molodtsov试图帮助建议杀死名为'EXCEL'的所有进程。这似乎是在寻找麻烦。已经有很多答案描述了在调用excel.quit()之后通过与COM互操作玩得很好来让进程停止的方法。如果你可以使它工作,这是最好的。

@Kevin Vuilleumier提出了将WM_CLOSE发送到Excel窗口的好建议。我打算测试一下。

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

qazxswpoi

我没有时间在C#中进行全面测试,但是我在Powershell中进行了快速测试,我遇到了Excel无法终止的问题,这种方法很有效。

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

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


17
投票

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

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

9
投票

规则 - 永远不要再使用那一个点

- 一个点

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

- 两个或多个点

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

- 例子

excel.Workbooks.Open(...)

7
投票

在您的代码中,您有:

excel.Workbooks

Open正在创建一个COM对象。然后,您从该COM对象调用VSTO and COM Interop函数。但是,在完成后,您不会释放COM对象。

处理COM对象时,这是一个常见问题。基本上,表达式中不应该有多个点,因为在完成后需要清理COM对象。

这个话题太大了,无法在答案中完全探索,但我认为你会发现Jake Ginnivan关于这个主题的文章非常有帮助:How to properly clean up Excel interop object in C#, 2012 edition

如果您厌倦了所有这些ReleaseComObject调用,您可能会发现这个问题很有用: var workbook = excel.Workbooks.Open("")


6
投票

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

Workbooks

创建一个没有引用的targetRange.Columns.AutoFit() 实例。

甚至参考如下:

.Columns()

将在您不知情的情况下创建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; } 函数用于您引用的任何内容:

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

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

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

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

Marshal.FinalReleaseComObject

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


3
投票

如其他答案所述,使用两个点将创建隐藏的引用,Marshal.FinalReleaseComObject无法关闭。我只是想分享我的解决方案,这消除了记住this.worksheet的需要 - 它很容易错过,并且找到罪魁祸首很痛苦。

我使用一个通用的IDisposable包装类,可以在任何COM对象上使用。它就像一个魅力,它保持一切美观和干净。我甚至可以重用私有字段(例如finally)。由于IDisposable的性质(Dispose方法作为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(); } } } } 运行),它还会在出现错误时自动释放对象。

here

1
投票

或者,您可以按照[DllImport("user32.dll", CharSet = CharSet.Auto)] private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam); 的说明终止Excel进程。

首先,导入SendMessage函数:

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

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

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

1
投票

如果你是绝望的。除非您了解它的作用,否则不要使用此方法:

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

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

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


1
投票

Cannot close Excel.exe after Interop process

不要让这太复杂!只需创建一个简单的方法并调用该方法如下:

               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);
        }
© www.soinside.com 2019 - 2024. All rights reserved.