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で実行されています。どんな助けでも素晴らしいでしょう!ありがとう
このような手動のメモリ管理は決して機能しません。これは非常に長い間知られていた問題であり、ガベージコレクターが発明された主な理由です。プログラマーは、メモリを解放することを永遠に忘れます。
使用されているメモリが見えないと、さらに難しくなります。これは確かにあなたのコードの場合ですが、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参照を保持できません。デバッガーなしでリリースビルドを実行したときと同様に、デバッグ時にも機能するようになりました。
System.Runtime.InteropServices.Marshal.FinalReleaseComObjectを試してみてください。これは役立つはずです...また、正しく思い出せば、xlWorkBook.Close()とxlapp.quitを呼び出す必要があります。最初にそれらを呼び出し、次にそれらを何にも設定しません。
GC.Collectは、どこに配置したかはあまり意味がありません。どちらかといえば、それを呼び出す必要があります戻った後 from converToExcel
。また、ファイナライザーが実行されるのを待つ必要がある場合もあります。個人的にはハンスの答えが道だと思いますが、C#でOfficeアドインを作成した個人的な経験から、特に古いOfficeバージョンとの互換性が必要な場合は、手動で参照カウントを行う必要があることがあります。 (特にオフィスからのイベントを処理する場合、手動の参照カウントによってのみ確実に解決できる多くの文書化された問題があります。また、一部のCOMライブラリは、GCによって間違った順序でリリースされた場合、まったく気に入らないものもありますが、そうではありません。オフィス付き。)
次に、コードの実際の問題について説明します。ここではリリースされていない3つの中間COMオブジェクトがあります。
xlWorkBook.Sheets
はタイプExcel.Sheets
のコレクションを返しますxlWorkSheet.Columns
はタイプExcel.Range
のCOMオブジェクトを返しますxlWorkSheet.Cells
はExcel.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を記述すると便利です(ラッパーをクラスではなく構造体にして、ヒープにラッパーを割り当てないようにします)。
'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
最終的に解決しました:)
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
このメソッドは、開かれた特定のプロセスを閉じます。
ワークブックを開いた直後に、コードにこの行を追加するのと同じくらい簡単です。
oExcel.displayalerts = False
何が起こっているのかを適切に対処している人は誰もいませんでした。代わりに、その回避策を作成しようとしました。
ここで起こっていることは、ワークブックがバックグラウンドで保存するように促しているということです。コードでは、ワークブックではなくワークシートを保存しています。それをだましてブックの保存状態を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()