我在C#(ApplicationClass
)中使用Excel互操作,并在我的finally子句中放置了以下代码:
while (System.Runtime.InteropServices.Marshal.ReleaseComObject(excelSheet) != 0) { } excelSheet = null; GC.Collect(); GC.WaitForPendingFinalizers();
虽然这种工作,Excel.exe
即使我关闭Excel后,该过程仍然在后台.只有在我的应用程序手动关闭后才会发布.
我做错了什么,或者是否有其他方法可以确保互操作对象得到妥善处理?
Excel不会退出,因为您的应用程序仍然保持对COM对象的引用.
我猜你是在调用COM对象的至少一个成员而不将其分配给变量.
对我来说,我直接使用excelApp.Worksheets对象而不将其分配给变量:
Worksheet sheet = excelApp.Worksheets.Open(...); ... Marshal.ReleaseComObject(sheet);
我不知道内部C#为Worksheets COM对象创建了一个包装器,它没有被我的代码释放(因为我不知道它),这也是为什么没有卸载Excel的原因.
我在这个页面找到了我的问题的解决方案,它也有一个很好的规则,用于在C#中使用COM对象:
切勿对COM对象使用两个点.
因此,有了这些知识,正确的做法是:
Worksheets sheets = excelApp.Worksheets; // <-- The important part Worksheet sheet = sheets.Open(...); ... Marshal.ReleaseComObject(sheets); Marshal.ReleaseComObject(sheet);
POST MORTEM更新:
我希望每个读者都能仔细阅读Hans Passant的这个答案,因为它解释了我和许多其他开发人员陷入困境的陷阱.当我几年前写这个答案时,我不知道调试器对垃圾收集器的影响,并得出了错误的结论.为了历史,我保持我的答案不变,但请阅读此链接,不要采用"两点"的方式:了解.NET中的垃圾收集和使用IDisposable清理Excel互操作对象
您实际上可以干净地释放Excel应用程序对象,但您必须小心.
维护一个命名引用的建议绝对是你访问的每个COM对象,然后通过显式发布它Marshal.FinalReleaseComObject()
在理论上是正确的,但不幸的是,在实践中很难管理.如果有人在任何地方滑动并使用"两个点",或者通过for each
循环或任何其他类似命令迭代单元格,那么你将拥有未引用的COM对象并冒着挂起的风险.在这种情况下,无法在代码中找到原因; 你必须通过眼睛审查你的所有代码,并希望找到原因,这个任务对于大型项目来说几乎是不可能的.
好消息是,您实际上不必维护对您使用的每个COM对象的命名变量引用.相反,调用GC.Collect()
然后GC.WaitForPendingFinalizers()
释放您没有引用的所有(通常是次要的)对象,然后显式释放您持有命名变量引用的对象.
您还应该按重要性的相反顺序发布命名引用:首先是范围对象,然后是工作表,工作簿,最后是Excel应用程序对象.
例如,假设您有一个名为Range的对象变量xlRng
,一个名为Worksheet的变量xlSheet
,一个名为的Workbook变量xlBook
和一个名为的Excel Application变量xlApp
,那么您的清理代码可能如下所示:
// 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),否则不需要这样做,因为它使用终结器来导致在终结队列中提升整个对象图.在下一次垃圾收集之前,这些对象不会被释放.但是,如果你不使用VSTO,你应该能够调用GC.Collect()
和GC.WaitForPendingFinalizers()
一次.
我知道明确地说GC.Collect()
是一个禁忌(当然这两次听起来很痛苦),但说实话,没有办法解决它.通过正常操作,您将生成隐藏的对象,您不会引用该对象,因此您无法通过除调用之外的任何其他方式释放GC.Collect()
.
这是一个复杂的话题,但这就是它的全部内容.为清理过程建立此模板后,您可以正常编码,无需包装等.:-)
我在这里有一个教程:
使用VB.Net/COM Interop自动化Office程序
它是为VB.NET编写的,但不要因此而被推迟,其原理与使用C#时的原理完全相同.
前言:我的回答包含两个解决方案,因此在阅读时要小心,不要错过任何内容.
有关如何卸载Excel实例的不同方法和建议,例如:
使用Marshal.FinalReleaseComObject()显式释放每个com对象(不要忘记隐式创建的com对象).要释放每个创建的com对象,可以使用此处提到的2点规则:
如何正确清理Excel互操作对象?
调用GC.Collect()和GC.WaitForPendingFinalizers()使CLR释放未使用的com对象*(实际上,它可以工作,请参阅我的第二个解决方案以获取详细信息)
检查com-server-application是否显示一个等待用户回答的消息框(虽然我不确定它是否可以阻止Excel关闭,但我听说过几次)
将WM_CLOSE消息发送到主Excel窗口
在单独的AppDomain中执行与Excel一起使用的功能.有些人认为卸载AppDomain时Excel实例将被关闭.
杀死我们的excel-interoping代码启动后实例化的所有excel实例.
但!有时候所有这些选择都没有帮助或不合适!
例如,昨天我发现在我的一个函数中(使用excel)Excel在函数结束后继续运行.我尝试了一切!我彻底检查了整个函数10次,并为所有内容添加了Marshal.FinalReleaseComObject()!我还有GC.Collect()和GC.WaitForPendingFinalizers().我检查了隐藏的消息框.我试图将WM_CLOSE消息发送到主Excel窗口.我在一个单独的AppDomain中执行了我的函数并卸载了该域.什么都没有帮助!关闭所有excel实例的选项是不合适的,因为如果用户手动启动另一个Excel实例,在执行我的函数期间也可以使用Excel,那么该实例也将被我的函数关闭.我打赌用户不会高兴!所以,老实说,这是一个蹩脚的选择(没有冒犯的人).所以我花了几个小时才找到一个好的(以我的拙见)解决方案:通过hWnd的主窗口杀死excel进程(这是第一个解决方案).
这是简单的代码:
[DllImport("user32.dll")] private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); ///Tries to find and kill process by hWnd to the main window of the process. /// Handle to the main window of the process. ///True if process was found and killed. False if process was not found by hWnd or if it could not be killed. 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; } ///Finds and kills process by hWnd to the main window of the process. /// Handle to the main window of the process. ////// Thrown when process is not found by the hWnd parameter (the process is not running). /// The identifier of the process might be expired. /// ///See Process.Kill() exceptions documentation. ///See Process.Kill() exceptions documentation. ///See Process.Kill() exceptions documentation. 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模式(我认为这里是合适的):如果Process不能被杀死,一个方法不会抛出异常(例如,进程不再存在) ,如果进程未被杀死,另一个方法抛出异常.此代码中唯一的弱点是安全权限.从理论上讲,用户可能没有权限终止该进程,但在99.99%的情况下,用户具有此类权限.我还用来宾帐户对它进行了测试 - 它完美无缺.
因此,使用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终止了!:)
好吧,让我们回到第二个解决方案,正如我在帖子开头所承诺的那样.
第二种解决方案是调用GC.Collect()和GC.WaitForPendingFinalizers().是的,他们确实有效,但你需要小心!
很多人说(我说)调用GC.Collect()没有帮助.但它无法帮助的原因是仍然有对COM对象的引用!GC.Collect()没有帮助的最常见原因之一是在调试模式下运行项目.在调试模式中,不再真正引用的对象在方法结束之前不会被垃圾收集.
因此,如果您尝试GC.Collect()和GC.WaitForPendingFinalizers()并且它没有帮助,请尝试执行以下操作:
1)尝试在发布模式下运行项目并检查Excel是否正确关闭
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将关闭=)
更新:添加了C#代码,并链接到Windows作业
我花了一些时间试图弄清楚这个问题,当时XtremeVBTalk是最活跃和最敏感的.这是我的原始帖子的链接,即使应用程序崩溃,也要干净地关闭Excel Interop进程.以下是帖子的摘要,以及复制到这篇文章的代码.
关闭Interop进程Application.Quit()
并Process.Kill()
在大多数情况下工作,但如果应用程序崩溃灾难性,则会失败.即如果应用程序崩溃,Excel进程仍将运行松散.
解决方案是让操作系统使用Win32调用通过Windows作业对象处理流程的清理.当您的主应用程序终止时,关联的进程(即Excel)也将终止.
我发现这是一个干净的解决方案,因为操作系统正在进行清理工作.您所要做的就是注册 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;
调用它.0x2000
是JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
枚举值,该值由MSDN定义为:
与作业关联的所有进程在作业的最后一个句柄关闭时终止.
额外的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对象的每个引用时,将其设置为null 非常重要.这包括细胞,表格和一切.
需要释放Excel命名空间中的任何内容.期
你做不到:
Worksheet ws = excel.WorkBooks[1].WorkSheets[1];
你必须这样做
Workbooks books = excel.WorkBooks; Workbook book = books[1]; Sheets sheets = book.WorkSheets; Worksheet ws = sheets[1];
然后释放物体.
首先 - 您永远不必打电话Marshal.ReleaseComObject(...)
或Marshal.FinalReleaseComObject(...)
在进行Excel互操作时.这是一个令人困惑的反模式,但是有关此的任何信息,包括来自Microsoft,表明您必须从.NET手动释放COM引用的任何信息都是不正确的.事实是.NET运行时和垃圾收集器正确地跟踪和清理COM引用.对于你的代码,这意味着你可以删除顶部的整个`while(...)循环.
其次,如果要确保在进程结束时清除对进程外COM对象的COM引用(以便Excel进程关闭),则需要确保垃圾收集器运行.通过调用GC.Collect()
和正确执行此操作GC.WaitForPendingFinalizers()
.调用这两次是安全的,并确保周期也被清理干净(虽然我不确定它是否需要,并且会欣赏一个显示这个的例子).
第三,当在调试器下运行时,本地引用将被人为地保持活动直到方法结束(以便局部变量检查工作).因此, GC.Collect()
调用对于清理对象无效,就像rng.Cells
使用相同的方法一样.您应该将执行COM interop的代码从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上的很多帖子(尤其是这个问题!).
最终让我深入了解并找出正确建议的是博客文章Marshal.ReleaseComObject Considered Dangerous以及在调试器下发现引用保持活动的问题令我困惑的早期测试.
我找到了一个有用的通用模板,可以帮助实现COM对象的正确处理模式,当它们超出范围时需要调用Marshal.ReleaseComObject:
用法:
using (AutoReleaseComObjectexcelApplicationWrapper = new AutoReleaseComObject (new Application())) { try { using (AutoReleaseComObject workbookWrapper = new AutoReleaseComObject (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: 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年....如果你已经创建了一个应用程序,你需要在删除链接之前先关闭它.
objExcel = new Excel.Application(); objBook = (Excel.Workbook)(objExcel.Workbooks.Add(Type.Missing));
关闭时
objBook.Close(true, Type.Missing, Type.Missing); objExcel.Application.Quit(); objExcel.Quit();
当您新建一个Excel应用程序时,它会在后台打开一个Excel程序.在释放链接之前,您需要命令excel程序退出,因为该Excel程序不是您的直接控制的一部分.因此,如果链接被释放,它将保持打开状态!
好好编程大家~~
常见的开发人员,你的解决方案都没有为我工作,所以我决定实施一个新技巧.
首先说明"我们的目标是什么?" >"我们在任务经理的工作后没有看到excel对象"
好.不要挑战并开始销毁它,但考虑不要破坏并行运行的其他实例os Excel.
因此,获取当前处理器的列表并获取EXCEL进程的PID,然后在完成任务后,我们在进程列表中有一个具有唯一PID的新guest虚拟机,找到并销毁那个.
<请记住,在您的Excel工作期间,任何新的Excel工艺都将被检测为新的并销毁> <更好的解决方案是捕获新创建的excel对象的PID并将其销毁>
Process[] prs = Process.GetProcesses(); ListexcelPID = new List (); 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正常关闭只有三个关键因素:
1:确保没有对您创建的excel应用程序的剩余引用(您应该只有一个;将其设置为null
)
2:打电话 GC.Collect()
3:必须通过用户手动关闭程序或通过调用Quit
Excel对象来关闭Excel.(请注意,Quit
就像用户试图关闭程序一样,如果有未保存的更改,则会显示确认对话框,即使Excel不可见.用户可以按取消,然后Excel将不会关闭. )
1需要在2之前发生,但3可以随时发生.
实现此目的的一种方法是使用您自己的类包装互操作Excel对象,在构造函数中创建互操作实例,并使用Dispose实现IDisposable类似于
if (!mDisposed) { mExcel = null; GC.Collect(); mDisposed = true; }
这将清除程序方面的优势.关闭Excel后(由用户手动或通过您调用Quit
),该过程将消失.如果程序已经关闭,则该过程将在GC.Collect()
通话中消失.
(我不确定它有多重要,但你可能需要在GC.WaitForPendingFinalizers()
通话后GC.Collect()
打个电话,但并不是绝对必须摆脱Excel过程.)
多年来,这对我没有任何问题.请记住,虽然这有效,但实际上你必须优雅地关闭才能工作.如果在清理Excel之前中断程序(通常在调试程序时点击"停止"),您仍将累积excel.exe进程.
为了增加Excel不关闭的原因,即使在读取,创建时为每个对象创建直接引用,也是"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
我传统上遵循VVS答案中的建议.但是,为了使这个答案与最新选项保持同步,我认为我未来的所有项目都将使用"NetOffice"库.
NetOffice是Office PIA的完全替代品,完全与版本无关.它是Managed COM包装器的集合,可以处理在.NET中使用Microsoft Office时经常导致此类令人头疼的清理.
一些关键功能是:
大多数版本无关(并且依赖于版本的功能)
没有依赖
没有PIA
没有注册
没有VSTO
我与该项目没有任何关系; 我真的很欣赏头痛的严重减少.
这里接受的答案是正确的,但也请注意,不仅需要避免"两点"引用,还需要避免通过索引检索的对象.您也不需要等到程序结束后才能清理这些对象,最好在可能的情况下创建一个在它们完成后立即清理它们的函数.这是我创建的一个函数,它分配一个Style对象的一些属性xlStyleHeader
:
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]
一个变量才能清理它(不是因为两个点,它引用了一个不需要释放的枚举,而是因为我所指的对象实际上是一个Border对象确实需要发布).
这种事情在标准应用程序中并不是必需的,它们可以很好地自行清理,但在ASP.NET应用程序中,如果你甚至错过其中一个,无论你多久调用一次垃圾收集器,Excel都会仍在您的服务器上运行.
在编写此代码时监视任务管理器时需要注意细节和许多测试执行,但这样做可以省去拼命搜索代码页以找到错过的一个实例的麻烦.这在循环中工作时尤其重要,在循环中,您需要释放对象的EACH INSTANCE,即使它每次循环时都使用相同的变量名.
尝试后
以相反的顺序释放COM对象
添加GC.Collect()
并GC.WaitForPendingFinalizers()
在最后两次
不超过两个点
关闭工作簿并退出应用程序
在发布模式下运行
对我有用的最终解决方案是移动一组
GC.Collect(); GC.WaitForPendingFinalizers();
我们将函数的末尾添加到包装器中,如下所示:
private void FunctionWrapper(string sourcePath, string targetPath) { try { FunctionThatCallsExcel(sourcePath, targetPath); } finally { GC.Collect(); GC.WaitForPendingFinalizers(); } }
¨°º¤ø"¸拍摄Excel触发并咀嚼泡泡糖¸"ø¤º°¨
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); }
我完全遵循了这一点......但我仍然遇到1000次中的问题.谁知道为什么.是时候拿出锤子......
在Excel Application类实例化之后,我得到了刚刚创建的Excel进程.
excel = new Microsoft.Office.Interop.Excel.Application(); var process = Process.GetProcessesByName("EXCEL").OrderByDescending(p => p.StartTime).First();
然后,一旦我完成了上述所有COM清理工作,我确保该进程没有运行.如果它还在运行,就杀掉它!
if (!process.HasExited) process.Kill();
您需要注意Excel对您正在运行的文化非常敏感.
在调用Excel函数之前,您可能会发现需要将文化设置为EN-US.这不适用于所有功能 - 但其中一些功能.
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;
即使您使用的是VSTO,这也适用.
有关详细信息:http://support.microsoft.com/default.aspx?scid = kb; en-us; Q320369
"永远不要在COM对象中使用两个点"是一个很好的经验法则,以避免COM引用的泄漏,但Excel PIA可能会导致泄漏,而不是一见钟情.
其中一种方法是订阅由任何Excel对象模型的COM对象公开的任何事件.
例如,订阅Application类的WorkbookOpen事件.
COM类通过回调接口公开一组事件.为了订阅事件,客户端代码可以简单地注册实现回调接口的对象,并且COM类将调用其方法以响应特定事件.由于回调接口是COM接口,因此实现对象的任务是减少它为任何事件处理程序接收(作为参数)的任何COM对象的引用计数.
Excel PIA将Excel Application类的COM事件公开为常规.NET事件.每当客户端代码订阅.NET事件(强调"a")时,PIA就会创建一个实现回调接口的类的实例,并将其注册到Excel.
因此,许多回调对象在Excel中注册,以响应来自.NET代码的不同订阅请求.每个事件订阅一个回调对象.
用于事件处理的回调接口意味着,PIA必须为每个.NET事件订阅请求订阅所有接口事件.它无法挑选.收到事件回调后,回调对象检查关联的.NET事件处理程序是否对当前事件感兴趣,然后调用处理程序或静默忽略回调.
所有这些回调对象都不会减少它们为任何回调方法接收的任何COM对象(作为参数)的引用计数(即使是对于被忽略的方法).它们仅依靠CLR垃圾收集器来释放COM对象.
由于GC运行是非确定性的,这可能导致Excel进程的持续时间超过预期,并产生"内存泄漏"的印象.
到目前为止唯一的解决方案是避免PIA的COM类事件提供程序,并编写自己的事件提供程序,确定性地释放COM对象.
对于Application类,可以通过实现AppEvents接口,然后使用IConnectionPointContainer接口向Excel注册实现来完成.Application类(以及使用回调机制公开事件的所有COM对象)实现IConnectionPointContainer接口.