使用VB.NET配置Excel com对象的正确方法?

时间:2012-04-25 04:27:27

标签: .net 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

4 个答案:

答案 0 :(得分:6)

@PanPizza C#和VB.NET非常相似,从行尾删除;Worksheets sheets = ...变为Dim sheets Worksheets = ...。如果你有兴趣在编程方面做得更好,你应该真正学会如何在两者之间进行转换,因为许多.NET示例只在一个或另一个中提供,而你实际上是在限制自己。

正如在这个答案中所提到的:How do I properly clean up Excel interop objects?“永远不要使用两个点”这意味着总是逐步进入一个子对象,永远不要这样做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池未分配内存,以便它可以轻松地在这个内存中实例化对象,而不必向操作系统请求更多内存。 / p>

答案 1 :(得分:5)

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

其次,如果要确保在进程结束时清除对进程外COM对象的COM引用(以便Excel进程关闭),则需要确保垃圾收集器运行。您可以通过拨打GC.Collect()GC.WaitForPendingFinalizers()来正确执行此操作。调用两次是安全的,结束确保周期也被清理干净。

第三,当在调试器下运行时,本地引用将被人为地保持活动直到方法结束(以便局部变量检查起作用)。因此,GC.Collect()调用对于从同一方法清除rng.Cells之类的对象无效。您应该将执行COM interop的代码从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上的许多帖子。

最终让我深入了解并找出正确建议的是这篇文章https://blogs.msdn.microsoft.com/visualstudio/2010/03/01/marshal-releasecomobject-considered-dangerous/以及在一些StackOverflow答案的调试器下发现引用保持活动的问题。

答案 2 :(得分:0)

我搜索并搜索了这个,甚至微软自己的解决方案都不起作用(Here如果你想看看)。我有一个vb.net应用程序,可以将数据导出到Excel模板。理想情况下,当用户关闭Excel窗口时,它会终止进程,但事实并非如此,因为正如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

它按窗口名称查找,而不是进程名称,并且仅杀死与其相关的进程。这是我可以让Excel正常关闭而不会弄乱任何东西的唯一方法。

答案 3 :(得分:0)

对我而言,关键是让GarbageCollector(GC)知道我想清理一些东西。我意识到这通常是没有必要的,但在使用COM对象时,有时是必要的。有关详细信息,请参阅此链接https://www.add-in-express.com/creating-addins-blog/2013/11/05/release-excel-com-objects/

发布对象后,请致电GCCollect(),要求WaitForPendingFinalizers()进行清理。上面的链接声明有必要调用这些mehtods两次,以便从内存中完全删除COM对象。在我的情况下,调用这些方法曾经有效,但值得两次调用它。

oBook.Close()
oExcel.Quit()

releaseObject(oExcel)
releaseObject(oBook)
releaseObject(oWS)

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