简体   繁体   English

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

[英]The proper way to dispose Excel com object using VB.NET?

I have following code (obtained from online tutorial). 我有以下代码(从在线教程获得)。 The code is working but I suspect the way to dispose the Excel com object is somewhat not proper . 代码正在运行,但我怀疑处理Excel com对象的方式有点不合适 Do we need really need to call GC.Collect? 我们真的需要调用GC.Collect吗? Or what is the best way to dispose this Excel com object? 或者,处理此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# and VB.NET are very similar, remove the ; @PanPizza C#和VB.NET非常相似,删除了; from the end of the line, Worksheets sheets = ... becomes Dim sheets Worksheets = ... . 从行的末尾开始, Worksheets sheets = ...变成Dim sheets Worksheets = ... If you're interested in getting better at programming you should really learn how to transition between both as many .NET examples are only provided in one or the other and you are really limiting yourself. 如果你有兴趣在编程方面做得更好,你应该真正学会如何在两者之间进行转换,因为许多.NET示例只在一个或另一个中提供,而你实际上是在限制自己。

As mentioned in this answer: How do I properly clean up Excel interop objects? 正如本回答中提到的: 如何正确清理Excel互操作对象? "Never use two dots" this means always step down into a single sub-object and never do this Dim oWS AS Excel.Worksheet = oExcel.Worksheets.Open(...) always step down to workbook and then step down to the worksheet, never directly from the Excel.Application . “永远不要使用两个点”这意味着总是逐步进入单个子对象并且永远不要这样做Dim oWS AS Excel.Worksheet = oExcel.Worksheets.Open(...)总是下到工作簿然后下到工作表,永远不要直接来自Excel.Application

As a general rule what you need to do is release your items in the reverse order to that which they were created. 作为一般规则,您需要做的是以与创建项目相反的顺序发布项目。 Otherwise you're taking the feet out from underneath your other references and they won't correctly deallocate. 否则你会从其他引用的下方取出它们并且它们将无法正确释放。

Notice how you create Excel Application ( oExcel ), then Excel Workbook ( oBook ) and then finally Excel Worksheet ( oWS ), you need to release them in the reverse order. 注意如何创建Excel应用程序( oExcel ),然后是Excel工作簿( oBook ),最后是Excel工作表( oWS ),您需要以相反的顺序发布它们。

Thus your code becomes: 因此,您的代码变为:

    oBook.Close()
    oExcel.Quit()

    releaseObject(oWS)
    releaseObject(oBook)
    releaseObject(oExcel)
Catch ex As Exception

and just remove this code entirely from the Sub releaseObject(ByVal obj As Object) 并完全从Sub releaseObject(ByVal obj As Object)删除此代码

Finally
    GC.Collect()

It's not needed, GC occurs naturally and don't expect your applications to instantly free up memory, .NET pools unallocated memory so that it can readily instance objects in this memory rather than having to ask the OS for more memory. 它不是必需的,GC自然发生,并且不希望你的应用程序立即释放内存,.NET池未分配内存,以便它可以很容易地在这个内存中实例化对象,而不是要求操作系统获得更多内存。

First - you never have to call Marshal.ReleaseComObject(...) or Marshal.FinalReleaseComObject(...) when doing Excel interop. 首先 - 在进行Excel互操作时,您永远不必调用Marshal.ReleaseComObject(...)Marshal.FinalReleaseComObject(...) It is a confusing anti-pattern, but any information about this, including from Microsoft, that indicates you have to manually release COM references from .NET is incorrect. 这是一个令人困惑的反模式,但是有关此的任何信息,包括来自Microsoft,表明您必须从.NET手动释放COM引用的任何信息都是不正确的。 The fact is that the .NET runtime and garbage collector correctly keep track of and clean up COM references. 事实是.NET运行时和垃圾收集器正确地跟踪和清理COM引用。 For your code, this means you can remove the whole releaseObject(...) Sub and calls to it. 对于您的代码,这意味着您可以删除整个releaseObject(...) Sub并调用它。

Second, if you want to ensure that the COM references to an out-of-process COM object is cleaned up when your process ends (so that the Excel process will close), you need to ensure that the Garbage Collector runs. 其次,如果要确保在进程结束时清除对进程外COM对象的COM引用(以便Excel进程关闭),则需要确保垃圾收集器运行。 You do this correctly with calls to GC.Collect() and GC.WaitForPendingFinalizers() . 您可以通过调用GC.Collect()GC.WaitForPendingFinalizers()来正确执行此操作。 Calling twice is safe, end ensures that cycles are definitely cleaned up too. 调用两次是安全的,结束确保周期也被清理干净。

Third, when running under the debugger, local references will be artificially kept alive until the end of the method (so that local variable inspection works). 第三,当在调试器下运行时,本地引用将被人为地保持活动直到方法结束(以便局部变量检查工作)。 So a GC.Collect() calls are not effective for cleaning object like rng.Cells from the same method. 因此GC.Collect()调用对于从同一方法清除rng.Cells对象rng.Cells You should split the code doing the COM interop from the GC cleanup into separate methods. 您应该将执行COM interop的代码从GC清理拆分为单独的方法。

The general pattern would be: 一般模式是:

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

There is a lot of false information and confusion about this issue, including many posts on MSDN and on StackOverflow. 关于这个问题存在很多虚假信息和混淆,包括MSDN和StackOverflow上的许多帖子。

What finally convinced me to have a closer look and figure out the right advice was this post https://blogs.msdn.microsoft.com/visualstudio/2010/03/01/marshal-releasecomobject-considered-dangerous/ together with finding the issue with references kept alive under the debugger on some StackOverflow answer. 什么最终说服我仔细看看并找出正确的建议是这篇文章https://blogs.msdn.microsoft.com/visualstudio/2010/03/01/marshal-releasecomobject-considered-dangerous/与找到在一些StackOverflow答案的调试器下,引用保持活动的问题。

I've searched and searched for this and even Microsoft's own solution doesn't work ( Here if you want to have a look). 我找啊找,这和(连微软自己的解决方案不起作用在这里 ,如果你想看看)。 I have a vb.net application that exports data to an Excel template. 我有一个vb.net应用程序,可以将数据导出到Excel模板。 Ideally when the user closes the Excel window it would kill the process but it doesn't because, as stated in the Microsoft article, vb.net is still referencing it. 理想情况下,当用户关闭Excel窗口时,它会终止进程,但事实并非如此,因为正如Microsoft文章中所述,vb.net仍在引用它。

You need to kill the process yourself, there is a procedure to do this as below: 您需要自己终止该过程,有一个过程可以执行此操作,如下所示:

For Each p As Process In Process.GetProcesses
     If p.ProcessName = "EXCEL.EXE" Then p.Kill
Next

However, this would kill all instances of Excel and the user may have other Excel windows open that would get shutdown without saving, so I've come up with this (the workbook I'm using is called "Top 5 Issues Template"): 但是,这会杀死所有Excel实例,并且用户可能打开其他Excel窗口而不保存就会关闭,所以我想出了这个(我正在使用的工作簿称为“前5个问题模板”):

For Each p As Process In Process.GetProcesses
     If InStr(p.MainWindowTitle, "Top 5 Issues Template") <> 0 Then p.Kill
Next

This looks by the window name, not process name, and kills only the process that is related to it. 这将按窗口名称查找,而不是进程名称,并仅杀死与其相关的进程。 This is the only way I could get Excel to close properly without messing anything up. 这是我可以让Excel正常关闭而不会弄乱任何东西的唯一方法。

The key for me was to let the GarbageCollector (GC) know I wanted something cleaned up. 对我来说关键是让GarbageCollector(GC)知道我想要清理一些东西。 I realize this usually isn't necessary, but when working with COM objects, it is sometimes necessary. 我意识到这通常是没有必要的,但在使用COM对象时,有时是必要的。 See this link for more information https://www.add-in-express.com/creating-addins-blog/2013/11/05/release-excel-com-objects/ 有关详细信息,请参阅此链接https://www.add-in-express.com/creating-addins-blog/2013/11/05/release-excel-com-objects/

After releasing the objects, ask the GC to clean up by calling Collect() and WaitForPendingFinalizers() . 释放对象后,通过调用Collect()WaitForPendingFinalizers()来要求GC清理。 The link above states it is necessary to call these mehtods twice in order to completely remove COM objects from memory. 上面的链接声明有必要调用这些mehtods两次,以便从内存中完全删除COM对象。 In my case, calling these methods once worked, but it may be worth calling it twice. 在我的情况下,调用这些方法曾经有效,但可能值得两次调用它。

oBook.Close()
oExcel.Quit()

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

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM