正确的方法使用VB.NET处置Excel的COM对象?正确的方法使用VB.NET处置Excel的CO

2019-05-13 15:26发布

我有以下代码(来自网上的教程获得)。 该代码是工作,但我怀疑处置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

Answer 1:

@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更多的内存。



Answer 2:

首先-你从来没有叫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的答案在调试器下保持活力。



Answer 3:

我找啊找,这和(连微软自己的解决方案不起作用在这里 ,如果你想看看)。 我将数据导出到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正确关闭不搞乱任何东西的唯一途径。



Answer 4:

对我来说,关键是让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()


文章来源: The proper way to dispose Excel com object using VB.NET?