使用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()