web-dev-qa-db-ja.com

ExcelプロセスがVB.netで閉じない

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ビットのWindows8で実行されています。どんな助けでも素晴らしいでしょう!ありがとう

8
jmcsmith

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

使用されているメモリが見えないと、さらに難しくなります。これは確かにあなたのコードの場合ですが、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()は、実際にはanyのインターフェイス参照を収集しません。 この投稿 でその動作の詳細を確認してください。

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

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

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

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

14
Hans Passant

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

1
Christian Sauer

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

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

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

これに加えて、Marshal.ReleaseComObjectが例外をスローした場合、手動参照カウントで何か間違ったことをしたので、例外ハンドラーでラップしません。手動の参照カウントを行う場合、COM-> NET境界を越えるたびにすべてのCOMオブジェクトを解放する必要があります。つまり、ループのすべての反復でExcel.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-clauses内でReleaseComObjectを呼び出します。汎用ラッパーを定義し、try-finallyの代わりにusing-clausesを記述すると便利です(ラッパーをクラスではなく構造体にして、ヒープにラッパーを割り当てないようにします)。

1
Zarat
'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
1
Gretchen

最終的に解決しました:)

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

このメソッドは、開かれた特定のプロセスを閉じます。

0
Xtian11

ワークブックを開いた直後に、コードにこの行を追加するのと同じくらい簡単です。

oExcel.displayalerts = False

0
SuJo RiBu

何が起こっているのかを適切に対処している人は誰もいませんでした。代わりに、その回避策を作成しようとしました。

ここで起こっていることは、ワークブックがバックグラウンドで保存するように促しているということです。コードでは、ワークブックではなくワークシートを保存しています。それをだましてブックの保存状態をtrueに設定するか、Excelアプリケーションを終了する前にブックを保存することができます。

私もこの問題を抱えていました。 Excelプロセスは、アプリケーションが開いている間ずっと実行されます。 xlWorkBook.Saved = True行を追加すると、xlApp.Quit()の呼び出し後にプロセスが終了します。私の場合、Excelファイルを保存する必要はなく、値を参照するだけです。

オプション#1-ブックを保存しないでください:

xlWorkSheet.SaveAs(fileLoc)
xlWorkBook.Saved = True       ' Add this line here.
'xlWorkBook.Close()           ' This line shouldn't be necessary anymore.
xlApp.Quit()

オプション#2-ワークブックを新しいファイルに保存します。

'xlWorkSheet.SaveAs(fileLoc)  ' Not needed
xlWorkBook.SaveAs(fileLoc)    ' If the file doesn't exist
'xlWorkBook.Close()           ' This line shouldn't be necessary anymore.
xlApp.Quit()

オプション#3-ワークブックを既存のファイルに保存します。

'xlWorkSheet.SaveAs(fileLoc)  ' Not needed
xlWorkBook.Save(fileLoc)      ' If the file does exist
'xlWorkBook.Close()           ' This line shouldn't be necessary anymore.
xlApp.Quit()

.Quit()メソッド:
https://docs.Microsoft.com/en-us/dotnet/api/Microsoft.office.interop.Excel._application.quit?view=Excel-pia#Microsoft_Office_Interop_Excel__Application_Quit

.Saved()メソッド:
https://docs.Microsoft.com/en-us/dotnet/api/Microsoft.office.interop.Excel._workbook.saved?view=Excel-pia#Microsoft_Office_Interop_Excel__Workbook_Saved

0
TinMan464