8

interop.excel を使用して Excel ファイルを作成していますが、プロセスが終了しません。これは私が使用しようとしているコードです。

 Private Sub converToExcel(fileLoc As String, ds As DataSet)
    Dim xlApp As Excel.Application
    Dim xlWorkBook As Excel.Workbook
    Dim xlWorkBooks As Excel.Workbooks
    Dim xlWorkSheet As Excel.Worksheet
    Dim misValue As Object = System.Reflection.Missing.Value
    Dim i As Integer
    Dim j As Integer

    xlApp = New Excel.Application
    xlWorkBooks = xlApp.Workbooks
    xlWorkBook = xlWorkBooks.Add(misValue)
    xlWorkSheet = xlWorkBook.Sheets("sheet1")

    For i = 0 To ds.Tables(0).Rows.Count - 1
        For j = 0 To ds.Tables(0).Columns.Count - 1
            xlWorkSheet.Columns.NumberFormat = "@"
            xlWorkSheet.Cells(i + 1, j + 1) = String.Format("{0}", ds.Tables(0).Rows(i).Item(j).ToString())
        Next
    Next

    xlWorkSheet.SaveAs(fileLoc)
    xlWorkBook.Close()
    xlApp.Quit()

    releaseObject(xlWorkSheet)
    releaseObject(xlWorkBook)
    releaseObject(xlWorkBooks)
    releaseObject(xlApp)

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

COM オブジェクトが見つからないと思いますが、解決策が見つからないようです。また、これは 64 ビットの Windows 8 で実行されていることに注意してください。ありがとう

4

8 に答える 8

17

このような手動のメモリ管理は機能しません。これは非常に長い間知られていた問題であり、ガベージコレクターが発明された主な理由です。プログラマーは、メモリを解放することを永遠に忘れます。

使用中のメモリが見えない場合は、さらに難しくなります。これは確かにあなたのコードに当てはまりますが、xlWorkSheet.Cells(i + 1, j + 1)式は3つ以上の参照を使用します。1つはCellsプロパティによって返される範囲オブジェクト用、1つはによって選択されたi+1サブ範囲オブジェクト用、もう1つはによって選択されたサブ範囲オブジェクト用j+1です。VB.NET言語によって提供される非常に優れたシンタックスシュガーであり、それなしでCOMコードを書くことは、かなり苦痛です。しかし、参照を表示するのに役立ちません。ソースコードでそれを見ることができないだけでなく、デバッガーがそれらを見るのを助けるためにできることは絶対にありません。

これは.NETで非常に解決された問題であり、ガベージコレクターがあり、すべてを見ることができます。最も基本的な問題は、問題を解決する機会を与えないことです。あなたが犯した間違いはあなたがやめたことです。おそらく、最後のステートメントにブレークポイントを設定し、タスクマネージャーを調べて、Excel.exeがまだ実行されていることを確認します。はい、それは正常です。ガベージコレクションは瞬時ではありません。

GC.Collect()を呼び出すとすぐに実行できるはずですが、プロジェクトのデバッグビルドを実行する特定のケースでは機能しません。その後、ローカル変数の有効期間がメソッドの最後まで延長され、Autos / Locals/Watchウィンドウでそれらを確認できるようになります。つまり、GC.Collect()は実際にはインターフェイス参照を収集しません。この投稿でその動作について詳しく説明します。

簡単な回避策は、停止しないことです。ガベージコレクターに実行する理由を与えるために有用なことを続けてください。または、プログラムが完了してから終了させると、ファイナライザスレッドが最後に実行されたときにExcelが終了します。これは、参照を持っていたローカル変数がスコープ内にないために機能します。

しかし、とにかく誰もが即座の修正を望んでいます。すべてのreleaseObject()呼び出しを削除することで取得できます。代わりに次のようにします。

converToExcel(path, dset)
GC.Collect()
GC.WaitForPendingFinalizers()

つまり、メソッドが戻った後にコレクションを強制します。ローカル変数はスコープ内にないため、Excel参照を保持できません。デバッガーなしでリリースビルドを実行したときと同様に、デバッグ時にも機能するようになりました。

于 2013-01-08T21:12:56.197 に答える
1

GC.Collect は、どこに配置してもあまり意味がありません。どちらかといえば、から戻った後にconverToExcel呼び出す必要があります。また、ファイナライザーが実行されるまで待つ必要がある場合もあります。個人的にはハンスの答えが正しいと思いますが、C# で Office アドインを書いた個人的な経験から、特に古い Office バージョンとの互換性が必要な場合は、手動で参照カウントを行う必要があることを知っています。(特に Office からのイベントを処理する場合、手動の参照カウントによってのみ確実に解決できる多くの問題が文書化されています。また、一部の COM ライブラリは、GC によって間違った順序でリリースされたときにまったく好まないものもありますが、そうではありませんオフィス付き。)

コード内の実際の問題について説明します。ここではリリースされていない中間 COM オブジェクトが 3 つあります。

  • xlWorkBook.Sheetsタイプのコレクションを返しますExcel.Sheets
  • xlWorkSheet.ColumnsタイプのCOMオブジェクトを返しますExcel.Range
  • xlWorkSheet.CellsExcel.Rangeオブジェクトも返す

これに加えて、Marshal.ReleaseComObject が例外をスローした場合、手動の参照カウントで何か問題があったため、例外ハンドラーでラップしません。手動で参照カウントを行う場合、すべての COM オブジェクトを、COM->NET 境界を越えるたびに 1Excel.Range回解放する必要があります。つまり、ループの反復ごとにオブジェクトを解放する必要があります。

Excelを適切に終了させるコードは次のとおりです。

Imports Microsoft.Office.Interop
Imports System.Runtime.InteropServices

Private Sub converToExcel(fileLoc As String, ds As DataSet)
    Dim xlApp As New Excel.Application
    Dim xlWorkBooks As Excel.Workbooks = xlApp.Workbooks
    Dim xlWorkBook As Excel.Workbook = xlWorkBooks.Add(System.Reflection.Missing.Value)
    Dim xlWorkSheets As Excel.Sheets = xlWorkBook.Sheets
    ' accessing the sheet by index because name is localized and your code will fail in non-english office versions
    Dim xlWorkSheet As Excel.Worksheet = xlWorkSheets(1)

    For i = 0 To ds.Tables(0).Rows.Count - 1
        For j = 0 To ds.Tables(0).Columns.Count - 1
            ' couldn't this be moved outside the loop?
            Dim xlColumns As Excel.Range = xlWorkSheet.Columns
            xlColumns.NumberFormat = "@"
            Marshal.ReleaseComObject(xlColumns)

            Dim xlCells As Excel.Range = xlWorkSheet.Cells
            xlCells(i + 1, j + 1) = ds.Tables(0).Rows(i).Item(j).ToString()
            Marshal.ReleaseComObject(xlCells)
        Next
    Next

    xlWorkSheet.SaveAs(fileLoc)
    'xlWorkBook.Close() -- not really necessary
    xlApp.Quit()

    Marshal.ReleaseComObject(xlWorkSheet)
    Marshal.ReleaseComObject(xlWorkSheets)
    Marshal.ReleaseComObject(xlWorkBook)
    Marshal.ReleaseComObject(xlWorkBooks)
    Marshal.ReleaseComObject(xlApp)
End Sub

特別な注意が必要な場合は、office API からの例外を処理し、finally 句内で ReleaseComObject を呼び出します。一般的なラッパーを定義し、try-finally の代わりに using-clauses を記述すると便利です (ラッパーをクラスではなく構造体にして、ヒープにラッパーを割り当てないようにします)。

于 2013-01-08T20:11:40.687 に答える
1

最後に解決しました:)

Private Function useSomeExcel(ByVal Excelfilename As String) 
  Dim objExcel As Excel.Application
  Dim objWorkBook As Excel.Workbook
  Dim objWorkSheets As Excel.Worksheet

  Dim datestart As Date = Date.Now
  objExcel = CreateObject("Excel.Application") 'This opens...  
  objWorkBook = objExcel.Workbooks.Open(Excelfilename) ' ... excel process
  Dim dateEnd As Date = Date.Now
  End_Excel_App(datestart, dateEnd) ' This closes excel proces
End Function

この方法を使用する

  Private Sub End_Excel_App(datestart As Date, dateEnd As Date)
    Dim xlp() As Process = Process.GetProcessesByName("EXCEL")
    For Each Process As Process In xlp
     If Process.StartTime >= datestart And Process.StartTime <= dateEnd Then
       Process.Kill()
       Exit For
     End If
    Next
  End Sub

このメソッドは、開いている特定のプロセスを閉じます。

于 2015-05-06T22:10:16.317 に答える
1

System.Runtime.InteropServices.Marshal.FinalReleaseComObject を試してみてください。これは役立つはずです...また、正しく思い出せば、xlWorkBook.Close() と xlapp.quit を呼び出す必要があります。最初にそれらを呼び出してから、何も設定しません。

于 2013-01-08T20:05:48.730 に答える
1
'Get the PID from the wHnd and kill the process.
' open the spreadsheet
ImportFileName = OpenFileDialog1.FileName
excel = New Microsoft.Office.Interop.Excel.ApplicationClass
wBook = excel.Workbooks.Open(ImportFileName)
hWnd = excel.Hwnd
Dim id As Integer = GetWindowThreadProcessId(hWnd, ExcelPID)

Sub CloseExcelFile()
        Try
            ' first try this
            wBook.Saved = True
            wBook.Close()
            excel.Quit()

            ' then this.
            System.Runtime.InteropServices.Marshal.ReleaseComObject(excel)
            excel = Nothing

            ' This appears to be the only way to close excel!
            Dim oProcess As Process
            oProcess = Process.GetProcessById(ExcelPID)
            If oProcess IsNot Nothing Then
                oProcess.Kill()
            End If

        Catch ex As Exception
            excel = Nothing
        Finally
            GC.Collect()
        End Try
    End Sub
于 2016-05-09T18:33:47.007 に答える
-2
Dim xlp() As Process = Process.GetProcessesByName("EXCEL")

 For Each Process As Process In xlp
   Process.Kill()
     If Process.GetProcessesByName("EXCEL").Count = 0 Then
       Exit For
     End If
 Next
于 2013-10-26T07:30:22.767 に答える