如何正确清理Excel互操作对象?

我在C#中使用Excel互操作( ApplicationClass ),并在我的finally子句中放置了下面的代码:

 while (System.Runtime.InteropServices.Marshal.ReleaseComObject(excelSheet) != 0) { } excelSheet = null; GC.Collect(); GC.WaitForPendingFinalizers(); 

虽然这样的作品, Excel.exe进程仍然在后台,即使我closuresExcel。 只有在我的应用程序被手动closures时才会被释放。

我做错了什么,或者有没有其他办法来确保互操作对象被妥善处置?

Excel不会退出,因为您的应用程序仍然保留对COM对象的引用。

我想你至less调用COM对象的一个​​成员,而不分配给一个variables。

对我来说,这是我直接使用的excelApp.Worksheets对象,而不用将它分配给一个variables:

 Worksheet sheet = excelApp.Worksheets.Open(...); ... Marshal.ReleaseComObject(sheet); 

我不知道内部C#为工作表 COM对象创build了一个包装,它没有被我的代码发布(因为我没有意识到),并且是为什么不卸载Excel的原因。

我在这个页面上find了解决我的问题的解决scheme,在C#中使用COM对象也有一个很好的规则:

切勿在COM对象中使用两个点。


所以有了这些知识,正确的做法是:

 Worksheets sheets = excelApp.Worksheets; // <-- The important part Worksheet sheet = sheets.Open(...); ... Marshal.ReleaseComObject(sheets); Marshal.ReleaseComObject(sheet); 

您实际上可以干净地释放您的Excel应用程序对象,但是您必须小心。

维护一个命名参考的build议绝对是你访问的所有COM对象,然后通过Marshal.FinalReleaseComObject()来明确地释放它,这在理论上是正确的,但不幸的是,在实践中非常难以pipe理。 如果有人滑过任何地方并使用“双点”,或者通过for each循环或任何其他类似的命令for each单元格进行迭代,那么您将拥有未引用的COM对象并冒着挂起的风险。 在这种情况下,将无法find代码中的原因; 你必须仔细检查你的所有代码,并希望find原因,这对大型项目来说几乎是不可能的。

好消息是,您实际上不必为每个使用的COM对象维护一个命名variables引用。 相反,调用GC.Collect() ,然后GC.WaitForPendingFinalizers()来释放所有不包含引用的(通常较小的)对象,然后显式释放您持有指定variables引用的对象。

您还应该按照相反的顺序释放命名的引用:范围对象,工作表,工作簿,最后是Excel应用程序对象。

例如,假设您有一个名为xlRng的Range对象variables,一个名为xlRng的Worksheetvariables,一个名为xlSheet的工作簿variables和一个名为xlApp的Excel应用程序variables,那么您的清理代码可能如下所示:

 // Cleanup GC.Collect(); GC.WaitForPendingFinalizers(); Marshal.FinalReleaseComObject(xlRng); Marshal.FinalReleaseComObject(xlSheet); xlBook.Close(Type.Missing, Type.Missing, Type.Missing); Marshal.FinalReleaseComObject(xlBook); xlApp.Quit(); Marshal.FinalReleaseComObject(xlApp); 

在大多数代码示例中,您将看到用于清理.NET中的COM对象, GC.Collect()GC.WaitForPendingFinalizers()调用是在TWICE中进行的,如下所示:

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

但是,这不应该是必需的,除非您正在使用Visual Studio Tools for Office(VSTO),它使用终结器,以便在终止队列中提升整个对象graphics。 这些对象直到下一次垃圾收集才会被释放。 但是,如果您不使用VSTO,则应该只能调用一次GC.Collect()GC.WaitForPendingFinalizers()

我知道明确地调用GC.Collect()是一个GC.Collect()当然,这两次听起来非常痛苦),但说实话,没有办法。 通过正常的操作,您将生成隐藏的对象,因此,除了调用GC.Collect()之外,您不能通过任何其他方式释放引用。

这是一个复杂的话题,但是这确实是一切。 一旦你为你的清理过程build立这个模板,你可以正常的编码,而不需要包装等.-)

我在这里有一个教程:

使用VB.Net / COM Interop自动执行Office程序

它是为VB.NET编写的,但不要被推迟,原则与使用C#时完全一样。

前言:我的答案包含两个解决scheme,所以在阅读时要小心,不要错过任何东西。

如何使Excel实例卸载有不同的方法和build议,如:

  • 使用Marshal.FinalReleaseComObject()显式释放EVERY com对象(不要忘记隐式创build的com对象)。 要释放每个创build的com对象,可以使用这里提到的2个点的规则:
    如何正确清理Excel互操作对象?

  • 调用GC.Collect()和GC.WaitForPendingFinalizers()使CLR释放未使用的COM对象*(实际上,它的工作原理,请参阅我的第二个解决scheme的细节)

  • 检查com-server-application是否显示一个消息框,等待用户回答(虽然我不确定它可以防止Excelclosures,但是我听说过几次)

  • 发送WM_CLOSE消息到主Excel窗口

  • 在单独的AppDomain中执行与Excel一起工作的函数。 有些人认为,当AppDomain被卸载时,Excel实例将被closures。

  • 杀死所有的excel实例,这些实例在我们的excel-interoping代码启动后被实例化。

但! 有时候所有这些选项都没有帮助或者不适合!

例如,昨天我发现,在我的一个函数(与Excel一起工作)中,Excel在函数结束后继续运行。 我尝试了一切! 我彻底检查了整个函数10次,并添加了Marshal.FinalReleaseComObject()的一切! 我也有GC.Collect()和GC.WaitForPendingFinalizers()。 我检查了隐藏的消息框。 我试图发送WM_CLOSE消息到主Excel窗口。 我在一个单独的AppDomain中执行我的function,并卸载该域。 没有帮助! closures所有excel实例的选项是不合适的,因为如果用户手动启动另一个Excel实例,在执行与Excel一起工作的函数时,那么该实例也将被我的函数closures。 我敢打赌,用户不会很高兴! 所以,说实话,这是一个蹩脚的select(没有冒犯家伙)。 所以我花了几个小时,才find一个好的(以我的愚见) 解决scheme通过主窗口的hWnd杀死excel进程 (这是第一个解决scheme)。

这里是简单的代码:

 [DllImport("user32.dll")] private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); /// <summary> Tries to find and kill process by hWnd to the main window of the process.</summary> /// <param name="hWnd">Handle to the main window of the process.</param> /// <returns>True if process was found and killed. False if process was not found by hWnd or if it could not be killed.</returns> public static bool TryKillProcessByMainWindowHwnd(int hWnd) { uint processID; GetWindowThreadProcessId((IntPtr)hWnd, out processID); if(processID == 0) return false; try { Process.GetProcessById((int)processID).Kill(); } catch (ArgumentException) { return false; } catch (Win32Exception) { return false; } catch (NotSupportedException) { return false; } catch (InvalidOperationException) { return false; } return true; } /// <summary> Finds and kills process by hWnd to the main window of the process.</summary> /// <param name="hWnd">Handle to the main window of the process.</param> /// <exception cref="ArgumentException"> /// Thrown when process is not found by the hWnd parameter (the process is not running). /// The identifier of the process might be expired. /// </exception> /// <exception cref="Win32Exception">See Process.Kill() exceptions documentation.</exception> /// <exception cref="NotSupportedException">See Process.Kill() exceptions documentation.</exception> /// <exception cref="InvalidOperationException">See Process.Kill() exceptions documentation.</exception> public static void KillProcessByMainWindowHwnd(int hWnd) { uint processID; GetWindowThreadProcessId((IntPtr)hWnd, out processID); if (processID == 0) throw new ArgumentException("Process has not been found by the given main window handle.", "hWnd"); Process.GetProcessById((int)processID).Kill(); } 

正如你所看到的,我提供了两种方法,根据Try-Parse模式(我认为这是适当的):如果进程不能被杀死(例如进程不再存在),一个方法不会抛出exception,另一种方法抛出exception如果进程没有被杀害。 这个代码中唯一的弱点是安全权限。 理论上,用户可能没有权限来终止进程,但在99.99%的情况下,用户拥有这样的权限。 我还testing了一个来宾帐户 – 它完美的作品。

因此,使用Excel的代码可以如下所示:

 int hWnd = xl.Application.Hwnd; // ... // here we try to close Excel as usual, with xl.Quit(), // Marshal.FinalReleaseComObject(xl) and so on // ... TryKillProcessByMainWindowHwnd(hWnd); 

瞧! Excel终止! 🙂

好的,让我们回到第二个解决scheme,正如我在文章开头所说的那样。 第二个解决scheme是调用GC.Collect()和GC.WaitForPendingFinalizers()。 是的,他们实际上工作,但你需要小心在这里!
很多人说(我说)调用GC.Collect()没有帮助。 但是它没有帮助的原因是,如果还有COM对象的引用! GC.Collect()没有帮助的一个最常见的原因是在debugging模式下运行项目。 在debugging模式下,不再真正引用的对象将不会被垃圾收集,直到方法结束。
所以,如果你尝试GC.Collect()和GC.WaitForPendingFinalizers(),并没有帮助,请尝试执行以下操作:

1)尝试在发布模式下运行你的项目,并检查Excel是否正确closures

2)用独立的方法包装使用Excel的方法。 所以,而不是像这样的东西:

 void GenerateWorkbook(...) { ApplicationClass xl; Workbook xlWB; try { xl = ... xlWB = xl.Workbooks.Add(...); ... } finally { ... Marshal.ReleaseComObject(xlWB) ... GC.Collect(); GC.WaitForPendingFinalizers(); } } 

你写:

 void GenerateWorkbook(...) { try { GenerateWorkbookInternal(...); } finally { GC.Collect(); GC.WaitForPendingFinalizers(); } } private void GenerateWorkbookInternal(...) { ApplicationClass xl; Workbook xlWB; try { xl = ... xlWB = xl.Workbooks.Add(...); ... } finally { ... Marshal.ReleaseComObject(xlWB) ... } } 

现在,Excel将closures=)

更新 :添加C#代码,并链接到Windows作业

我花了一些时间试图找出这个问题,当时XtremeVBTalk是最积极和响应。 这是一个链接到我原来的post,即使你的应用程序崩溃closures一个Excel Interop过程干净 。 以下是post的摘要,以及复制到此帖的代码。

  • 使用Application.Quit()Process.Kill()closuresInterop进程大部分工作,但如果应用程序崩溃灾难性地失败。 也就是说,如果应用程序崩溃,Excel进程仍然会运行。
  • 解决方法是让操作系统通过使用Win32调用的Windows作业对象来处理清理过程。 当你的主要应用程序死亡,相关的进程(即Excel)也将被终止。

我发现这是一个干净的解决scheme,因为操作系统正在做清理工作。 你所要做的就是注册 Excel进程。

Windows作业代码

包装Win32 API调用来注册Interop进程。

 public enum JobObjectInfoType { AssociateCompletionPortInformation = 7, BasicLimitInformation = 2, BasicUIRestrictions = 4, EndOfJobTimeInformation = 6, ExtendedLimitInformation = 9, SecurityLimitInformation = 5, GroupInformation = 11 } [StructLayout(LayoutKind.Sequential)] public struct SECURITY_ATTRIBUTES { public int nLength; public IntPtr lpSecurityDescriptor; public int bInheritHandle; } [StructLayout(LayoutKind.Sequential)] struct JOBOBJECT_BASIC_LIMIT_INFORMATION { public Int64 PerProcessUserTimeLimit; public Int64 PerJobUserTimeLimit; public Int16 LimitFlags; public UInt32 MinimumWorkingSetSize; public UInt32 MaximumWorkingSetSize; public Int16 ActiveProcessLimit; public Int64 Affinity; public Int16 PriorityClass; public Int16 SchedulingClass; } [StructLayout(LayoutKind.Sequential)] struct IO_COUNTERS { public UInt64 ReadOperationCount; public UInt64 WriteOperationCount; public UInt64 OtherOperationCount; public UInt64 ReadTransferCount; public UInt64 WriteTransferCount; public UInt64 OtherTransferCount; } [StructLayout(LayoutKind.Sequential)] struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION { public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation; public IO_COUNTERS IoInfo; public UInt32 ProcessMemoryLimit; public UInt32 JobMemoryLimit; public UInt32 PeakProcessMemoryUsed; public UInt32 PeakJobMemoryUsed; } public class Job : IDisposable { [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] static extern IntPtr CreateJobObject(object a, string lpName); [DllImport("kernel32.dll")] static extern bool SetInformationJobObject(IntPtr hJob, JobObjectInfoType infoType, IntPtr lpJobObjectInfo, uint cbJobObjectInfoLength); [DllImport("kernel32.dll", SetLastError = true)] static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process); private IntPtr m_handle; private bool m_disposed = false; public Job() { m_handle = CreateJobObject(null, null); JOBOBJECT_BASIC_LIMIT_INFORMATION info = new JOBOBJECT_BASIC_LIMIT_INFORMATION(); info.LimitFlags = 0x2000; JOBOBJECT_EXTENDED_LIMIT_INFORMATION extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION(); extendedInfo.BasicLimitInformation = info; int length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION)); IntPtr extendedInfoPtr = Marshal.AllocHGlobal(length); Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false); if (!SetInformationJobObject(m_handle, JobObjectInfoType.ExtendedLimitInformation, extendedInfoPtr, (uint)length)) throw new Exception(string.Format("Unable to set information. Error: {0}", Marshal.GetLastWin32Error())); } #region IDisposable Members public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } #endregion private void Dispose(bool disposing) { if (m_disposed) return; if (disposing) {} Close(); m_disposed = true; } public void Close() { Win32.CloseHandle(m_handle); m_handle = IntPtr.Zero; } public bool AddProcess(IntPtr handle) { return AssignProcessToJobObject(m_handle, handle); } } 

关于构造器代码的注意事项

  • 在构造函数中, info.LimitFlags = 0x2000; 叫做。 0x2000JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE枚举值,该值由MSDN定义为:

导致与作业关联的所有进程在作业的最后一个句柄closures时终止。

额外的Win32 API调用获取进程ID(PID)

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

使用代码

  Excel.Application app = new Excel.ApplicationClass(); Job job = new Job(); uint pid = 0; Win32.GetWindowThreadProcessId(new IntPtr(app.Hwnd), out pid); job.AddProcess(Process.GetProcessById((int)pid).Handle); 

这对于我正在开发的一个项目是有效的:

 excelApp.Quit(); Marshal.ReleaseComObject (excelWB); Marshal.ReleaseComObject (excelApp); excelApp = null; 

我们了解到,当您完成对Excel COM对象的每个引用时,将其设置为空是非常重要的。 这包括细胞,床单,和一切。

任何在Excel命名空间中的东西都需要被释放。

你不能这样做:

 Worksheet ws = excel.WorkBooks[1].WorkSheets[1]; 

你必须做的

 Workbooks books = excel.WorkBooks; Workbook book = books[1]; Sheets sheets = book.WorkSheets; Worksheet ws = sheets[1]; 

随后释放物体。

我发现了一个有用的通用模板,可以帮助实现COM对象的正确处理模式,当它们超出范围时,需要调用Marshal.ReleaseComObject:

用法:

 using (AutoReleaseComObject<Application> excelApplicationWrapper = new AutoReleaseComObject<Application>(new Application())) { try { using (AutoReleaseComObject<Workbook> workbookWrapper = new AutoReleaseComObject<Workbook>(excelApplicationWrapper.ComObject.Workbooks.Open(namedRangeBase.FullName, false, false, missing, missing, missing, true, missing, missing, true, missing, missing, missing, missing, missing))) { // do something with your workbook.... } } finally { excelApplicationWrapper.ComObject.Quit(); } } 

模板:

 public class AutoReleaseComObject<T> : IDisposable { private T m_comObject; private bool m_armed = true; private bool m_disposed = false; public AutoReleaseComObject(T comObject) { Debug.Assert(comObject != null); m_comObject = comObject; } #if DEBUG ~AutoReleaseComObject() { // We should have been disposed using Dispose(). Debug.WriteLine("Finalize being called, should have been disposed"); if (this.ComObject != null) { Debug.WriteLine(string.Format("ComObject was not null:{0}, name:{1}.", this.ComObject, this.ComObjectName)); } //Debug.Assert(false); } #endif public T ComObject { get { Debug.Assert(!m_disposed); return m_comObject; } } private string ComObjectName { get { if(this.ComObject is Microsoft.Office.Interop.Excel.Workbook) { return ((Microsoft.Office.Interop.Excel.Workbook)this.ComObject).Name; } return null; } } public void Disarm() { Debug.Assert(!m_disposed); m_armed = false; } #region IDisposable Members public void Dispose() { Dispose(true); #if DEBUG GC.SuppressFinalize(this); #endif } #endregion protected virtual void Dispose(bool disposing) { if (!m_disposed) { if (m_armed) { int refcnt = 0; do { refcnt = System.Runtime.InteropServices.Marshal.ReleaseComObject(m_comObject); } while (refcnt > 0); m_comObject = default(T); } m_disposed = true; } } } 

参考:

http://www.deez.info/sengelha/2005/02/11/useful-idisposable-class-3-autoreleasecomobject/

我不能相信这个问题已经困扰了世界5年….如果你已经创build了一个应用程序,你需要先closures它,然后再删除链接。

 objExcel = new Excel.Application(); objBook = (Excel.Workbook)(objExcel.Workbooks.Add(Type.Missing)); 

closures时

 objBook.Close(true, Type.Missing, Type.Missing); objExcel.Application.Quit(); objExcel.Quit(); 

当你新build一个excel应用程序时,它会在后台打开一个excel程序。 您需要命令该excel程序在您释放链接之前退出,因为该excel程序不是您直接控制的一部分。 因此,如果链接被释放,它将保持打开!

好的编程大家~~

共同的开发人员,你的解决scheme没有为我工作,所以我决定实施一个新的伎俩

首先请指定“我们的目标是什么?” =>“在任务pipe理器中不能看到excel对象”

好。 不要挑战并开始破坏它,但考虑不要破坏并行运行的其他实例操作系统。

所以,得到当前处理器的列表并获取EXCEL进程的PID,然后一旦你的工作完成,我们有一个新的客户进程列表中有一个唯一的PID,find并销毁那一个。

<记住任何新的excel过程在你的excel工作中将被检测为新的并被破坏> <一个更好的解决scheme是捕获新创build的excel对象的PID,并将其销毁>

 Process[] prs = Process.GetProcesses(); List<int> excelPID = new List<int>(); foreach (Process p in prs) if (p.ProcessName == "EXCEL") excelPID.Add(p.Id); .... // your job prs = Process.GetProcesses(); foreach (Process p in prs) if (p.ProcessName == "EXCEL" && !excelPID.Contains(p.Id)) p.Kill(); 

这解决了我的问题,希望你也是。

首先 – 在执行Excel互操作时,您永远不必调用Marshal.ReleaseComObject(...)Marshal.FinalReleaseComObject(...) 。 这是一个令人困惑的反模式,但任何有关此信息(包括来自Microsoft的信息)都表明您必须从.NET手动发布COM引用是不正确的。 事实是,.NET运行时和垃圾收集器正确地跟踪和清理COM引用。 对于你的代码,这意味着你可以删除顶部的整个while(…)循环。

其次,如果要确保在进程结束时清理进程外COM对象的COM引用(以便Excel进程将closures),则需要确保运行垃圾收集器。 您可以通过调用GC.Collect()GC.WaitForPendingFinalizers()来正确执行此操作。 调用这两次是安全的,并确保周期也肯定清理(虽然我不知道这是需要的,并希望有一个例子显示这一点)。

第三,在debugging器下运行时,局部引用将被人为地保留直到方法结束(因此局部variables检查工作)。 所以GC.Collect()调用对于像从同一个方法中的rng.Cells清理对象是rng.Cells的。 您应该将执行GC互操作的代码从GC清理分解成单独的方法。 (对于我来说,这是一个重要的发现,从@nightcoder发布在这里的答案的一部分)。

一般模式将因此是:

 Sub WrapperThatCleansUp() ' NOTE: Don't call Excel objects in here... ' Debugger would keep alive until end, preventing GC cleanup ' Call a separate function that talks to Excel DoTheWork() ' Now let the GC clean up (twice, to clean up cycles too) GC.Collect() GC.WaitForPendingFinalizers() GC.Collect() GC.WaitForPendingFinalizers() End Sub Sub DoTheWork() Dim app As New Microsoft.Office.Interop.Excel.Application Dim book As Microsoft.Office.Interop.Excel.Workbook = app.Workbooks.Add() Dim worksheet As Microsoft.Office.Interop.Excel.Worksheet = book.Worksheets("Sheet1") app.Visible = True For i As Integer = 1 To 10 worksheet.Cells.Range("A" & i).Value = "Hello" Next book.Save() book.Close() app.Quit() ' NOTE: No calls the Marshal.ReleaseComObject() are ever needed End Sub 

关于这个问题有很多虚假的信息和混淆,包括MSDN和Stack Overflow上的许多post(尤其是这个问题!)。

什么最终说服我有一个更仔细的看,找出正确的build议是博客文章Marshal.ReleaseComObject认为危险一起find引用保持活着的debugging器下的问题,这是混淆了我以前的testing。

这里接受的答案是正确的,但是也要注意,不仅需要避免“双点”引用,而且还要通过索引检索对象。 你也不需要等到程序完成清理这些对象时,最好创build一些函数,只要有可能就清理干净。 这是我创build的一个函数,它指定了一个名为xlStyleHeader的Style对象的一些属性:

 public Excel.Style xlStyleHeader = null; private void CreateHeaderStyle() { Excel.Styles xlStyles = null; Excel.Font xlFont = null; Excel.Interior xlInterior = null; Excel.Borders xlBorders = null; Excel.Border xlBorderBottom = null; try { xlStyles = xlWorkbook.Styles; xlStyleHeader = xlStyles.Add("Header", Type.Missing); // Text Format xlStyleHeader.NumberFormat = "@"; // Bold xlFont = xlStyleHeader.Font; xlFont.Bold = true; // Light Gray Cell Color xlInterior = xlStyleHeader.Interior; xlInterior.Color = 12632256; // Medium Bottom border xlBorders = xlStyleHeader.Borders; xlBorderBottom = xlBorders[Excel.XlBordersIndex.xlEdgeBottom]; xlBorderBottom.Weight = Excel.XlBorderWeight.xlMedium; } catch (Exception ex) { throw ex; } finally { Release(xlBorderBottom); Release(xlBorders); Release(xlInterior); Release(xlFont); Release(xlStyles); } } private void Release(object obj) { // Errors are ignored per Microsoft's suggestion for this type of function: // http://support.microsoft.com/default.aspx/kb/317109 try { System.Runtime.InteropServices.Marshal.ReleaseComObject(obj); } catch { } } 

请注意,我必须将xlBorders[Excel.XlBordersIndex.xlEdgeBottom]设置为一个variables来清除它(不是因为两个点,这是指不需要释放的枚举,而是因为对象I' m引用实际上是一个需要释放的Border对象)。

这种事情在标准的应用程序中并不是非常必要的,而在ASP.NET应用程序中,如果你甚至错过了其中的一个,那么无论你多长时间一次调用垃圾收集器,Excel都会仍然在您的服务器上运行。

在编写代码时,需要注意细节和许多testing执行,同时监视任务pipe理器,但这样做可以节省拼命search代码页的麻烦,以find您错过的一个实例。 当在循环中工作时,这是特别重要的,在循环中需要释放对象的EACH INSTANCE,即使每次循环使用相同的variables名称。

为了增加Excel不closures的原因,甚至当您在读取时创build对每个对象的直接引用,创build,就是“For”循环。

 For Each objWorkBook As WorkBook in objWorkBooks 'local ref, created from ExcelApp.WorkBooks to avoid the double-dot objWorkBook.Close 'or whatever FinalReleaseComObject(objWorkBook) objWorkBook = Nothing Next 'The above does not work, and this is the workaround: For intCounter As Integer = 1 To mobjExcel_WorkBooks.Count Dim objTempWorkBook As Workbook = mobjExcel_WorkBooks.Item(intCounter) objTempWorkBook.Saved = True objTempWorkBook.Close(False, Type.Missing, Type.Missing) FinalReleaseComObject(objTempWorkBook) objTempWorkBook = Nothing Next 

这肯定似乎是过于复杂。 从我的经验来看,只有三件关键的事情才能让Excel正常closures:

1:确保没有剩余的引用到您创build的Excel应用程序(您应该只有一个;将其设置为null

2:调用GC.Collect()

3:Excel必须closures,或者由用户手动closures程序,或者通过在Excel对象上调用Quit来完成。 (Note that Quit will function just as if the user tried to close the program, and will present a confirmation dialog if there are unsaved changes, even if Excel is not visible. The user could press cancel, and then Excel will not have been closed.)

1 needs to happen before 2, but 3 can happen anytime.

One way to implement this is to wrap the interop Excel object with your own class, create the interop instance in the constructor, and implement IDisposable with Dispose looking something like

 if (!mDisposed) { mExcel = null; GC.Collect(); mDisposed = true; } 

That will clean up excel from your program's side of things. Once Excel is closed (manually by the user or by you calling Quit ) the process will go away. If the program has already been closed, then the process will disappear on the GC.Collect() call.

(I'm not sure how important it is, but you may want a GC.WaitForPendingFinalizers() call after the GC.Collect() call but it is not strictly necessary to get rid of the Excel process.)

This has worked for me without issue for years. Keep in mind though that while this works, you actually have to close gracefully for it to work. You will still get accumulating excel.exe processes if you interrupt your program before Excel is cleaned up (usually by hitting "stop" while your program is being debugged).

After trying

  1. Release COM objects in reverse order
  2. Add GC.Collect() and GC.WaitForPendingFinalizers() twice at the end
  3. No more than two dots
  4. Close workbook and quit application
  5. Run in release mode

the final solution that works for me is to move one set of

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

that we added to the end of the function to a wrapper, as follows:

 private void FunctionWrapper(string sourcePath, string targetPath) { try { FunctionThatCallsExcel(sourcePath, targetPath); } finally { GC.Collect(); GC.WaitForPendingFinalizers(); } } 

I've traditionally followed the advice found in VVS's answer . However, in an effort to keep this answer up-to-date with the latest options, I think all my future projects will use the "NetOffice" library, available on CodePlex .

NetOffice is a complete replacement for the Office PIAs and is completely version-agnostic. It's a collection of Managed COM wrappers that can handle the cleanup that often causes such headaches when working with Microsoft Office in .NET.

Some key features are: – Mostly version-independent (and version-dependant features are documented) – No dependencies – No PIA – No registration – No VSTO

I am in no way affiliated with the project; I just genuinely appreciate the stark reduction in headaches.

You need to be aware that Excel is very sensitive to the culture you are running under as well.

You may find that you need to set the culture to EN-US before calling Excel functions. This does not apply to all functions – but some of them.

  CultureInfo en_US = new System.Globalization.CultureInfo("en-US"); System.Threading.Thread.CurrentThread.CurrentCulture = en_US; string filePathLocal = _applicationObject.ActiveWorkbook.Path; System.Threading.Thread.CurrentThread.CurrentCulture = orgCulture; 

This applies even if you are using VSTO.

For details: http://support.microsoft.com/default.aspx?scid=kb;en-us;Q320369

"Never use two dots with COM objects" is a great rule of thumb to avoid leakage of COM references, but Excel PIA can lead to leakage in more ways than apparent at first sight.

One of these ways is subscribing to any event exposed by any of the Excel object model's COM objects.

For example, subscribing to the Application class's WorkbookOpen event.

Some theory on COM events

COM classes expose a group of events through call-back interfaces. In order to subscribe to events, the client code can simply register an object implementing the call-back interface and the COM class will invoke its methods in response to specific events. Since the call-back interface is a COM interface, it is the duty of the implementing object to decrement the reference count of any COM object it receives (as a parameter) for any of the event handlers.

How Excel PIA expose COM Events

Excel PIA exposes COM events of Excel Application class as conventional .NET events. Whenever the client code subscribes to a .NET event (emphasis on 'a'), PIA creates an instance of a class implementing the call-back interface and registers it with Excel.

Hence, a number of call-back objects get registered with Excel in response to different subscription requests from the .NET code. One call-back object per event subscription.

A call-back interface for event handling means that, PIA has to subscribe to all interface events for every .NET event subscription request. It cannot pick and choose. On receiving an event call-back, the call-back object checks if the associated .NET event handler is interested in the current event or not and then either invokes the handler or silently ignores the call-back.

Effect on COM instance reference counts

All these call-back objects do not decrement the reference count of any of the COM objects they receive (as parameters) for any of the call-back methods (even for the ones that are silently ignored). They rely solely on the CLR garbage collector to free up the COM objects.

Since GC run is non-deterministic, this can lead to the holding off of Excel process for a longer duration than desired and create an impression of a 'memory leak'.

The only solution as of now is to avoid the PIA's event provider for the COM class and write your own event provider which deterministically releases COM objects.

For the Application class, this can be done by implementing the AppEvents interface and then registering the implementation with Excel by using IConnectionPointContainer interface . The Application class (and for that matter all COM objects exposing events using callback mechanism) implements the IConnectionPointContainer interface.

As others have pointed out, you need to create an explicit reference for every Excel object you use, and call Marshal.ReleaseComObject on that reference, as described in this KB article . You also need to use try/finally to ensure ReleaseComObject is always called, even when an exception is thrown. Ie instead of:

 Worksheet sheet = excelApp.Worksheets(1) ... do something with sheet 

you need to do something like:

 Worksheets sheets = null; Worksheet sheet = null try { sheets = excelApp.Worksheets; sheet = sheets(1); ... } finally { if (sheets != null) Marshal.ReleaseComObject(sheets); if (sheet != null) Marshal.ReleaseComObject(sheet); } 

You also need to call Application.Quit before releasing the Application object if you want Excel to close.

As you can see, this quickly becomes extremely unwieldy as soon as you try to do anything even moderately complex. I have successfully developed .NET applications with a simple wrapper class that wraps a few simple manipulations of the Excel object model (open a workbook, write to a Range, save/close the workbook etc). The wrapper class implements IDisposable, carefully implements Marshal.ReleaseComObject on every object it uses, and does not pubicly expose any Excel objects to the rest of the app.

But this approach doesn't scale well for more complex requirements.

This is a big deficiency of .NET COM Interop. For more complex scenarios, I would seriously consider writing an ActiveX DLL in VB6 or other unmanaged language to which you can delegate all interaction with out-proc COM objects such as Office. You can then reference this ActiveX DLL from your .NET application, and things will be much easier as you will only need to release this one reference.

I followed this exactly… But I still ran into issues 1 out of 1000 times. Who knows why. Time to bring out the hammer…

Right after the Excel Application class is instantiated I get a hold of the Excel process that was just created.

 excel = new Microsoft.Office.Interop.Excel.Application(); var process = Process.GetProcessesByName("EXCEL").OrderByDescending(p => p.StartTime).First(); 

Then once I've done all the above COM clean-up, I make sure that process isn't running. If it is still running, kill it!

 if (!process.HasExited) process.Kill(); 

¨°º¤ø„¸ Shoot Excel proc and chew bubble gum ¸„ø¤º°¨

 public class MyExcelInteropClass { Excel.Application xlApp; Excel.Workbook xlBook; public void dothingswithExcel() { try { /* Do stuff manipulating cells sheets and workbooks ... */ } catch {} finally {KillExcelProcess(xlApp);} } static void KillExcelProcess(Excel.Application xlApp) { if (xlApp != null) { int excelProcessId = 0; GetWindowThreadProcessId(xlApp.Hwnd, out excelProcessId); Process p = Process.GetProcessById(excelProcessId); p.Kill(); xlApp = null; } } [DllImport("user32.dll")] static extern int GetWindowThreadProcessId(int hWnd, out int lpdwProcessId); } 

When all the stuff above didn't work, try giving Excel some time to close its sheets:

 app.workbooks.Close(); Thread.Sleep(500); // adjust, for me it works at around 300+ app.Quit(); ... FinalReleaseComObject(app); 

Make sure that you release all objects related to Excel!

I spent a few hours by trying several ways. All are great ideas but I finally found my mistake: If you don't release all objects, none of the ways above can help you like in my case. Make sure you release all objects including range one!

 Excel.Range rng = (Excel.Range)worksheet.Cells[1, 1]; worksheet.Paste(rng, false); releaseObject(rng); 

The options are together here .

The two dots rule did not work for me. In my case I created a method to clean my resources as follows:

 private static void Clean() { workBook.Close(); Marshall.ReleaseComObject(workBook); excel.Quit(); CG.Collect(); CG.WaitForPendingFinalizers(); } 

You should be very careful using Word/Excel interop applications. After trying all the solutions we still had a lot of "WinWord" process left open on server (with more than 2000 users).

After working on the problem for hours, I realized that if I open more than a couple of documents using Word.ApplicationClass.Document.Open() on different threads simultaneously, IIS worker process (w3wp.exe) would crash leaving all WinWord processes open!

So I guess there is no absolute solution to this problem, but switching to other methods such as Office Open XML development.

A great article on releasing COM objects is 2.5 Releasing COM Objects (MSDN).

The method that I would advocate is to null your Excel.Interop references if they are non-local variables, and then call GC.Collect() and GC.WaitForPendingFinalizers() twice. Locally scoped Interop variables will be taken care of automatically.

This removes the need to keep a named reference for every COM object.

Here's an example taken from the article:

 public class Test { // These instance variables must be nulled or Excel will not quit private Excel.Application xl; private Excel.Workbook book; public void DoSomething() { xl = new Excel.Application(); xl.Visible = true; book = xl.Workbooks.Add(Type.Missing); // These variables are locally scoped, so we need not worry about them. // Notice I don't care about using two dots. Excel.Range rng = book.Worksheets[1].UsedRange; } public void CleanUp() { book = null; xl.Quit(); xl = null; GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); GC.WaitForPendingFinalizers(); } } 

These words are straight from the article:

In almost all situations, nulling the RCW reference and forcing a garbage collection will clean up properly. If you also call GC.WaitForPendingFinalizers, garbage collection will be as deterministic as you can make it. That is, you'll be pretty sure exactly when the object has been cleaned up—on the return from the second call to WaitForPendingFinalizers. As an alternative, you can use Marshal.ReleaseComObject. However, note that you are very unlikely to ever need to use this method.

我的解决scheme

 [DllImport("user32.dll")] static extern int GetWindowThreadProcessId(int hWnd, out int lpdwProcessId); private void GenerateExcel() { var excel = new Microsoft.Office.Interop.Excel.Application(); int id; // Find the Excel Process Id (ath the end, you kill him GetWindowThreadProcessId(excel.Hwnd, out id); Process excelProcess = Process.GetProcessById(id); try { // Your code } finally { excel.Quit(); // Kill him ! excelProcess.Kill(); } 

The accepted answer did not work for me. The following code in the destructor did the job.

 if (xlApp != null) { xlApp.Workbooks.Close(); xlApp.Quit(); } System.Diagnostics.Process[] processArray = System.Diagnostics.Process.GetProcessesByName("EXCEL"); foreach (System.Diagnostics.Process process in processArray) { if (process.MainWindowTitle.Length == 0) { process.Kill(); } } 

I think that some of that is just the way that the framework handles Office applications, but I could be wrong. On some days, some applications clean up the processes immediately, and other days it seems to wait until the application closes. In general, I quit paying attention to the details and just make sure that there aren't any extra processes floating around at the end of the day.

Also, and maybe I'm over simplifying things, but I think you can just…

 objExcel = new Excel.Application(); objBook = (Excel.Workbook)(objExcel.Workbooks.Add(Type.Missing)); DoSomeStuff(objBook); SaveTheBook(objBook); objBook.Close(false, Type.Missing, Type.Missing); objExcel.Quit(); 

Like I said earlier, I don't tend to pay attention to the details of when the Excel process appears or disappears, but that usually works for me. I also don't like to keep Excel processes around for anything other than the minimal amount of time, but I'm probably just being paranoid on that.

As some have probably already written, it's not just important how you close the Excel (object); it's also important how you open it and also by the type of the project.

In a WPF application, basically the same code is working without or with very few problems.

I have a project in which the same Excel file is being processed several times for different parameter value – eg parsing it based on values inside a generic list.

I put all Excel-related functions into the base class, and parser into a subclass (different parsers use common Excel functions). I didn't want that Excel is opened and closed again for each item in a generic list, so I've opened it only once in the base class and close it in the subclass. I had problems when moving the code into a desktop application. I've tried many of the above mentioned solutions. GC.Collect() was already implemented before, twice as suggested.

Then I've decided that I will move the code for opening Excel to a subclass. Instead of opening only once, now I create a new object (base class) and open Excel for every item and close it at the end. There is some performance penalty, but based on several tests Excel processes are closing without problems (in debug mode), so also temporary files are removed. I will continue with testing and write some more if I will get some updates.

The bottom line is: You must also check the initialize code, especially if you have many classes, etc.

使用:

 [DllImport("user32.dll")] private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); 

Declare it, add code in the finally block:

 finally { GC.Collect(); GC.WaitForPendingFinalizers(); if (excelApp != null) { excelApp.Quit(); int hWnd = excelApp.Application.Hwnd; uint processID; GetWindowThreadProcessId((IntPtr)hWnd, out processID); Process[] procs = Process.GetProcessesByName("EXCEL"); foreach (Process p in procs) { if (p.Id == processID) p.Kill(); } Marshal.FinalReleaseComObject(excelApp); } }