6

私は次のコードを持っています(オンラインチュートリアルから取得)。コードは機能していますが、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

4 に答える 4

8

まず、Excelの相互運用機能を実行するときに、電話をかける必要はありません。これは紛らわしいアンチパターンですが、Microsoftを含め、これに関する情報は、.NETからCOM参照を手動で解放する必要があることを示しています。実際のところ、.NETランタイムとガベージコレクターはCOM参照を正しく追跡し、クリーンアップします。あなたのコードにとって、これはあなたがSub全体を削除してそれを呼び出すことができることを意味します。Marshal.ReleaseComObject(...)Marshal.FinalReleaseComObject(...)releaseObject(...)

次に、プロセスの終了時に(Excelプロセスが閉じるように)アウトプロセスCOMオブジェクトへのCOM参照がクリーンアップされるようにする場合は、ガベージコレクターが実行されるようにする必要があります。とへの呼び出しでこれを正しく行いGC.Collect()ますGC.WaitForPendingFinalizers()。2回呼び出すのは安全です。終了すると、サイクルも確実にクリーンアップされます。

第3に、デバッガーで実行する場合、ローカル参照はメソッドが終了するまで人為的に存続します(ローカル変数検査が機能するように)。したがって、同じメソッドからのGC.Collect()ようにオブジェクトをクリーニングするには、呼び出しは効果的ではありません。rng.CellsGCクリーンアップからの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の回答で、デバッガーの下で参照が存続する問題。

于 2016-06-29T22:30:21.213 に答える
6

@PanPizza C#とVB.NETは非常に似ており;、行末からを削除すると、にWorksheets sheets = ...なりDim sheets Worksheets = ...ます。プログラミングを上手に行うことに興味がある場合は、両方の間で移行する方法を実際に学ぶ必要があります。多くの.NETの例はどちらか一方でのみ提供されており、実際に自分自身を制限しているからです。

この回答で述べたように:Excel相互運用オブジェクトを適切にクリーンアップするにはどうすればよいですか?「2つのドットを使用しない」とは、常に1つのサブオブジェクトにDim oWS AS Excel.Worksheet = oExcel.Worksheets.Open(...)ステップダウンし、ワークブックにステップダウンしてからワークシートにステップダウンすることを意味します。直接からではありませんExcel.Application

原則として、あなたがする必要があるのは、それらが作成されたのとは逆の順序であなたのアイテムをリリースすることです。そうしないと、他の参照の下から足を離してしまい、正しく割り当てが解除されません。

oExcelExcelアプリケーション( )、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にメモリの追加を要求することなく、このメモリ内のオブジェクトを簡単にインスタンス化できます。

于 2012-04-25T06:10:38.973 に答える
1

私にとっての鍵は、GarbageCollector(GC)に何かをクリーンアップしたいことを知らせることでした。通常、これは必要ないことはわかっていますが、COMオブジェクトを操作する場合は、必要になることもあります。詳細については、このリンクを参照してくださいhttps://www.add-in-express.com/creating-addins-blog/2013/11/05/release-excel-com-objects/

オブジェクトを解放した後、GCとを呼び出してクリーンアップするCollect()よう に依頼しWaitForPendingFinalizers()ます。上記のリンクは、COMオブジェクトをメモリから完全に削除するために、これらのメソッドを2回呼び出す必要があることを示しています。私の場合、これらのメソッドを呼び出すことは一度は機能しましたが、2回呼び出す価値があるかもしれません。

oBook.Close()
oExcel.Quit()

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

GC.Collect()
GC.WaitForPendingFinalizers()
GC.Collect()
GC.WaitForPendingFinalizers()
于 2015-05-21T16:05:07.270 に答える
0

私はこれを検索して検索しましたが、Microsoft独自のソリューションでさえ機能しません(ここで確認したい場合)。データをExcelテンプレートにエクスポートするvb.netアプリケーションがあります。理想的には、ユーザーがExcelウィンドウを閉じると、プロセスが強制終了されますが、Microsoftの記事に記載されているように、vb.netがまだそれを参照しているためです。

プロセスを自分で強制終了する必要があります。これを行う手順は次のとおりです。

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

ただし、これによりExcelのすべてのインスタンスが強制終了され、ユーザーが他のExcelウィンドウを開いて保存せずにシャットダウンする可能性があるため、これを思いつきました(使用しているブックは「Top5IssuesTemplate」と呼ばれます)。

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

これは、プロセス名ではなくウィンドウ名で検索され、それに関連するプロセスのみを強制終了します。これは、何も混乱させることなくExcelを適切に閉じることができる唯一の方法です。

于 2013-09-13T08:38:12.417 に答える