Interop进程后无法关闭Excel.exe

我遇到了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它应该工作,但没有。 我怎样才能解决这个问题?

谢谢。

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

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

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

所以,正确的方法是:

 var workbooks = excel.Workbooks; var workbook = workbooks.Open(/*params*/) //business logic here Marshal.ReleaseComObject(workbook); Marshal.ReleaseComObject(workbooks); Marshal.ReleaseComObject(excel); 

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

  ///  /// Disposes the current  object and cleans up any resources. ///  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(); } 

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

– 一个点

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

– 两个或多个点

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

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

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

这个话题太大了,无法在答案中完全探索,但我认为你会发现Jake Ginnivan关于这个主题的文章非常有用: VSTO和COM Interop

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

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

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

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

甚至参考如下:

 targetRange.Columns.AutoFit() 

将在您不知道并且未正确发布的情况下创建.Columns()的实例。

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

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

  public List _interopObjectList = new List(); 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(); } ///  /// Release a com interop object ///  ///  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(); 

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

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

首先,导入SendMessage函数:

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

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

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

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

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

 using Microsoft.Office.Interop.Excel; public class ExcelService { private _Worksheet worksheet; private class ComObject : 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(new Application())) { var excelInstance = comApplication.Instance; excelInstance.Visible = false; excelInstance.DisplayAlerts = false; try { using (var workbooks = new ComObject(excelInstance.Workbooks)) using (var workbook = new ComObject<_workbook>(workbooks.Instance.Add())) using (var comSheets = new ComObject(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(this.worksheet.Range["A4"])) using (var comWindow = new ComObject(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(); } } } } 

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

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

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

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

我有同样的问题,我们可以解决这个问题,没有任何杀戮,我们总是忘记关闭我们使用的接口,从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); } 

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

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

如果由于某种原因需要终止Excel App对象的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() } } 

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

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

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