使用VB.NET处理Excel COM对象的正确方法是什么?
我有以下代码(从在线教程获得)。 代码正在工作,但我怀疑处理Excel COM对象的方式有点不正确 。 我们是否真的需要调用GC.Collect? 或者什么是最好的方式来处置这个Excel COM对象?
Public Sub t1() Dim oExcel As New Excel.Application Dim oBook As Excel.Workbook = oExcel.Workbooks.Open(TextBox2.Text) 'select WorkSheet based on name Dim oWS As Excel.Worksheet = CType(oBook.Sheets("Sheet1"), Excel.Worksheet) Try oExcel.Visible = False 'now showing the cell value MessageBox.Show(oWS.Range(TextBox6.Text).Text) oBook.Close() oExcel.Quit() releaseObject(oExcel) releaseObject(oBook) releaseObject(oWS) Catch ex As Exception MsgBox("Error: " & ex.ToString, MsgBoxStyle.Critical, "Error!") End Try End Sub Private Sub releaseObject(ByVal obj As Object) Try System.Runtime.InteropServices.Marshal.ReleaseComObject(obj) obj = Nothing Catch ex As Exception obj = Nothing Finally GC.Collect() End Try End Sub
@PanPizza C#和VB.NET非常相似,删除了;
从行的末尾开始, Worksheets sheets = ...
变成Dim sheets Worksheets = ...
如果你有兴趣在编程上做得更好,你应该学会如何在两者之间进行转换,尽可能多的.NET范例只能在其中一个提供,而你真的限制了自己。
正如在这个答案中提到的: 如何正确清理Excel互操作对象? “永远不要使用两个点”,这意味着总是步入一个单一的子对象,永远不要这样做Dim oWS AS Excel.Worksheet = oExcel.Worksheets.Open(...)
总是下降到工作簿,然后下降到工作表,从来没有直接从Excel.Application
。
一般来说,你需要做的是按照与创build它们相反的顺序来释放你的项目。 否则,你会从下面的其他引用下脚,他们不会正确释放。
请注意,如何创buildExcel应用程序( oExcel
),然后创buildExcel工作簿( oBook
),最后创buildExcel工作表( oWS
),您需要按相反的顺序将其释放。
因此你的代码变成:
oBook.Close() oExcel.Quit() releaseObject(oWS) releaseObject(oBook) releaseObject(oExcel) Catch ex As Exception
只需从Sub releaseObject(ByVal obj As Object)
完全删除此代码,
Finally GC.Collect()
不需要,GC自然发生,不要期望你的应用程序立即释放内存,.NET池会分配未分配的内存,这样它可以轻松地在这个内存中实例化对象,而不必向操作系统请求更多的内存。
首先 – 在执行Excel互操作时,您永远不必调用Marshal.ReleaseComObject(...)
或Marshal.FinalReleaseComObject(...)
。 这是一个令人困惑的反模式,但任何有关此信息(包括来自Microsoft的信息)都表明您必须从.NET手动发布COM引用是不正确的。 事实是,.NET运行时和垃圾收集器正确地跟踪和清理COM引用。 对于您的代码,这意味着您可以删除整个releaseObject(...)
Sub并调用它。
其次,如果要确保在进程结束时清理进程外COM对象的COM引用(以便Excel进程将closures),则需要确保运行垃圾收集器。 您可以通过调用GC.Collect()
和GC.WaitForPendingFinalizers()
来正确执行此操作。 调用两次是安全的,最终确保循环也被清除。
第三,在debugging器下运行时,局部引用将被人为地保留直到方法结束(因此局部variables检查工作)。 因此, GC.Collect()
调用对于从同一方法清除对象rng.Cells
。 您应该将执行GC互操作的代码从GC清理分解成单独的方法。
一般的模式是:
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和StackOverflow上的许多post。
什么最终说服我有一个更仔细的审视,并找出正确的build议是这个职位https://blogs.msdn.microsoft.com/visualstudio/2010/03/01/marshal-releasecomobject-considered-dangerous/一起寻找问题引用在debugging器上一些StackOverflow答案保持活着。
我search了这个,甚至微软自己的解决scheme都不起作用( 这里如果你想看看)。 我有一个将数据导出到Excel模板的vb.net应用程序。 理想情况下,当用户closuresExcel窗口时,会终止进程,但并不是因为如Microsoft文章所述,vb.net仍在引用它。
你需要自己杀死进程,有一个过程如下:
For Each p As Process In Process.GetProcesses If p.ProcessName = "EXCEL.EXE" Then p.Kill Next
但是,这将杀死Excel的所有实例,用户可能会打开其他Excel窗口而不保存,因此我想出了这个(我正在使用的工作簿被称为“前5个问题模板”):
For Each p As Process In Process.GetProcesses If InStr(p.MainWindowTitle, "Top 5 Issues Template") <> 0 Then p.Kill Next
这看起来是通过窗口名称,而不是进程名称,并只杀死与之相关的进程。 这是我能够正确closuresExcel而不会搞乱任何东西的唯一方法。
对我来说关键是让GarbageCollector(GC)知道我想清理一些东西。 我意识到这通常是没有必要的,但是当使用COM对象时,有时是必要的。 请参阅此链接了解更多信息https://www.add-in-express.com/creating-addins-blog/2013/11/05/release-excel-com-objects/
释放对象之后,通过调用Collect()
和WaitForPendingFinalizers()
来要求GC
清理。 上面的链接声明有必要调用这些方法两次,以便从内存中完全删除COM对象。 在我的情况下,调用这些方法曾经工作过,但它可能是值得的两次。
oBook.Close() oExcel.Quit() releaseObject(oExcel) releaseObject(oBook) releaseObject(oWS) GC.Collect() GC.WaitForPendingFinalizers() GC.Collect() GC.WaitForPendingFinalizers()