簡體   English   中英

使用VB.NET配置Excel com對象的正確方法?

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

我有以下代碼(從在線教程獲得)。 代碼正在運行,但我懷疑處理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池未分配內存,以便它可以很容易地在這個內存中實例化對象,而不是要求操作系統獲得更多內存。

首先 - 在進行Excel互操作時,您永遠不必調用Marshal.ReleaseComObject(...)Marshal.FinalReleaseComObject(...) 這是一個令人困惑的反模式,但是有關此的任何信息,包括來自Microsoft,表明您必須從.NET手動釋放COM引用的任何信息都是不正確的。 事實是.NET運行時和垃圾收集器正確地跟蹤和清理COM引用。 對於您的代碼,這意味着您可以刪除整個releaseObject(...) Sub並調用它。

其次,如果要確保在進程結束時清除對進程外COM對象的COM引用(以便Excel進程關閉),則需要確保垃圾收集器運行。 您可以通過調用GC.Collect()GC.WaitForPendingFinalizers()來正確執行此操作。 調用兩次是安全的,結束確保周期也被清理干凈。

第三,當在調試器下運行時,本地引用將被人為地保持活動直到方法結束(以便局部變量檢查工作)。 因此GC.Collect()調用對於從同一方法清除rng.Cells對象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答案的調試器下,引用保持活動的問題。

我找啊找,這和(連微軟自己的解決方案不起作用在這里 ,如果你想看看)。 我有一個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正常關閉而不會弄亂任何東西的唯一方法。

對我來說關鍵是讓GarbageCollector(GC)知道我想要清理一些東西。 我意識到這通常是沒有必要的,但在使用COM對象時,有時是必要的。 有關詳細信息,請參閱此鏈接https://www.add-in-express.com/creating-addins-blog/2013/11/05/release-excel-com-objects/

釋放對象后,通過調用Collect()WaitForPendingFinalizers()來要求GC清理。 上面的鏈接聲明有必要調用這些mehtods兩次,以便從內存中完全刪除COM對象。 在我的情況下,調用這些方法曾經有效,但可能值得兩次調用它。

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