我有以下代码(来自网上的教程获得)。 该代码是工作,但我怀疑处置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
。
当你需要做的一般规则是释放在颠倒顺序来创建它们自己的商品。 否则,你从其他引用下服用英尺,他们将无法正常解除分配。
注意如何创建Excel应用程序( oExcel
),则Excel工作簿( oBook
),然后最后Excel工作表( 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池未分配的内存,以便它可以很容易地比如在这个内存对象,而不是问OS更多的内存。
首先-你从来没有叫Marshal.ReleaseComObject(...)
或Marshal.FinalReleaseComObject(...)
做的Excel互操作时。 这是一个令人困惑的反模式,但是关于这个任何信息,包括来自微软,表示你必须手动释放COM引用从.NET不正确。 事实是,.NET运行库和垃圾收集正确的跟踪和清理COM引用。 对于你的代码,这意味着你可以删除整个releaseObject(...)
Sub和调用它。
第二,如果你想确保工艺外的一个COM对象的COM引用被清理,当你处理结束(使Excel进程将关闭),你需要确保垃圾收集器运行。 你通过调用这样做正确GC.Collect()
和GC.WaitForPendingFinalizers()
调用两次是安全的,最终确保了循环肯定清理了。
三,调试器下运行时,局部引用会被人为地活着,直到方法结束(使局部变量检查工作)。 因此GC.Collect()
调用不是有效用于清洁对象像rng.Cells
从相同的方法。 你应该拆分代码从GC清理到不同的方法做的COM互操作。
一般模式是:
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上很多帖子。
什么终于说服我定睛一看,找出正确的意见是这篇文章https://blogs.msdn.microsoft.com/visualstudio/2010/03/01/marshal-releasecomobject-considered-dangerous/一起寻找与引用问题上的一些StackOverflow的答案在调试器下保持活力。
我找啊找,这和(连微软自己的解决方案不起作用在这里 ,如果你想看看)。 我将数据导出到Excel模板vb.net应用。 理想的情况是当用户关闭Excel窗口,将终止该进程,但它没有,因为作为微软的文章中指出,vb.net仍然引用它。
你需要自己杀的过程中,有一个过程,如下做到这一点:
For Each p As Process In Process.GetProcesses
If p.ProcessName = "EXCEL.EXE" Then p.Kill
Next
然而,这会杀了Excel的所有实例,用户可能有其他Excel打开窗户,将获得关机,不保存,所以我想出了这个(我使用该工作簿被称为“五大问题模板”):
For Each p As Process In Process.GetProcesses
If InStr(p.MainWindowTitle, "Top 5 Issues Template") <> 0 Then p.Kill
Next
这看起来由窗口名称,而不是进程名,并杀死只关系到它的进程。 这是我能得到的Excel正确关闭不搞乱任何东西的唯一途径。
对我来说,关键是让GarbageCollector(GC)知道我想要的东西清理。 我知道这通常是没有必要的,但随着COM对象时,它有时是必要的。 请参阅此链接了解更多信息https://www.add-in-express.com/creating-addins-blog/2013/11/05/release-excel-com-objects/
释放对象后,要求GC
通过调用清理Collect()
和WaitForPendingFinalizers()
上面的链路状态,有必要在为了从存储器中完全删除COM对象两次调用这些mehtods。 在我的情况下,调用这些方法曾经工作过,但它可能是值得调用它的两倍。
oBook.Close()
oExcel.Quit()
releaseObject(oExcel)
releaseObject(oBook)
releaseObject(oWS)
GC.Collect()
GC.WaitForPendingFinalizers()
GC.Collect()
GC.WaitForPendingFinalizers()