VB2008 初心者のはるです。初投稿させて頂きます。
環境は以下の通りです。
Excel:Excel2007
VB.net:VB2008express SP1
OS:WinXPsp3
以下はExcelのオープンから解放までのコーディングを抜粋しております。
Private Sub btnSelect_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnSelect.Click
Dim exlExcelApp As New Excel.Application
Dim exlBooks As Excel.Workbooks = exlExcelApp.Workbooks
Dim exlExcelBook As Excel.Workbook
Dim exlExcelSheet As Excel.Worksheet
For Each intFileList In Me.lstTargetFile.SelectedIndices
'lstTargetFileリストボックスで選択されているExcelファイル分処理
exlExcelBook = exlExcelApp.Workbooks.Open(strDirPath & "\" & Me.lstTargetFile.Items(intFileList))
Dim exlSheets As Excel.Sheets = exlExcelBook.Worksheets
exlExcelApp.Visible = True
exlExcelApp.DisplayAlerts = False
For i = 1 To exlExcelBook.Sheets.Count
exlExcelSheet = exlExcelBook.Sheets(i)
Dim exlCells As Excel.Range = exlExcelSheet.Cells
For iRows = intEmployeeDataFromRows To intEmployeeDataToRows
'全シートのexlCellsに対する参照処理
Next
MRComObject(exlCells)
exlCells = Nothing
MRComObject(exlExcelSheet)
exlExcelSheet = Nothing
Next
If Not exlExcelBook Is Nothing Then
Try
exlExcelBook.Close(False)
Finally
MRComObject(exlExcelBook)
End Try
End If
exlExcelBook = Nothing
MRComObject(exlSheets)
exlSheets = Nothing
MRComObject(exlBooks)
exlBooks = Nothing
exlExcelApp.Quit()
intFileCnt = intFileCnt + 1
Next
'Excelの結果に応じて、画面のListViewの編集処理
If Not exlExcelApp Is Nothing Then
Try
exlExcelApp.Quit()
Finally
MRComObject(exlExcelApp)
End Try
End If
exlExcelApp = Nothing
End Sub
Public Shared Sub MRComObject(Of T As Class)(ByRef objCom As T, Optional ByVal force As Boolean = False)
If objCom Is Nothing Then
Return
End If
Try
If System.Runtime.InteropServices.Marshal.IsComObject(objCom) Then
If force Then
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(objCom)
Else
Dim count As Integer = System.Runtime.InteropServices.Marshal.ReleaseComObject(objCom)
End If
End If
Finally
objCom = Nothing
End Try
End Sub
[質問]
上記の処理で数回処理した場合は閉じるボタン等でExcel.EXEがプロセスから消えてくれます。
しかし、10回起動を越したぐらいからかプロセスにExcel.EXEが5個以上残ってしまった時、
終了してもプロセスから消えてくれません。
たぶん、複数のCOMがオープンの度にExcel.EXEを起動してて、残骸がどんどん増えてるようですが・・。
いろいろ試してみたのですが、どこかおかしいのかわからない状態に陥ってしまったので、
ご教授をお願いしたく投稿させて頂きました。
宜しくお願い致します。
> exlExcelBook = exlExcelApp.Workbooks.Open(strDirPath & "\" & Me.lstTargetFile.Items(intFileList))
exlExcelApp.Workbooks.Open ではなく、
exlBooks.Open を使うべきかと。せっかく変数に取ってあるのですし。
> exlExcelApp.Visible = True
> exlExcelApp.DisplayAlerts = False
For Each ループの内側で、これらを毎回実行する必要は無い気がします。
> For i = 1 To exlExcelBook.Sheets.Count
exlExcelBook.Sheets.Count ではなく、
exlSheets.Count を使うべきかと。
> exlExcelSheet = exlExcelBook.Sheets(i)
exlExcelBook.Sheets(i) ではなく、
exlSheets(i) を使うべきかと。
> Dim exlCells As Excel.Range = exlExcelSheet.Cells
> For iRows = intEmployeeDataFromRows To intEmployeeDataToRows
> '全シートのexlCellsに対する参照処理
> Next
この中で、ReleaseComObject が行われていない場所はありませんか?
> MRComObject(exlCells)
> exlCells = Nothing
変数への Nothing 代入は、既に MRComObject 内で行われているようですが、
それだけでは不足だったのでしょうか?
> If Not exlExcelBook Is Nothing Then
この時点で、exlExcelBook が Nothing になる事は無いように思えます。
> If Not exlExcelBook Is Nothing Then
> Try
> exlExcelBook.Close(False)
> Finally
> MRComObject(exlExcelBook)
> End Try
> End If
> exlExcelBook = Nothing
> MRComObject(exlSheets)
> exlSheets = Nothing
> MRComObject(exlBooks)
> exlBooks = Nothing
Range → Sheet → Workbook → Sheets → Workbooks の順に解放していますが、
念のため、下位のオブジェクトから行っていった方が良いと思います。
Range → Sheet → Sheets → Workbook → Workbooks の順に書き換えてみてください。
> exlExcelApp.Quit()
> intFileCnt = intFileCnt + 1
> Next
> 'Excelの結果に応じて、画面のListViewの編集処理
> If Not exlExcelApp Is Nothing Then
> Try
> exlExcelApp.Quit()
ループ内の exlExcelApp.Quit は問題があるように思えます。
Quit するなら、ReleaseComObject もセットで行うべきですし、
Quit した後で、2回目以降に exlExcelApp を使い続けるべきではありません。
> If Not exlExcelApp Is Nothing Then
> Try
> exlExcelApp.Quit()
> Finally
> MRComObject(exlExcelApp)
> End Try
> End If
> exlExcelApp = Nothing
最後の Nothing 代入は、何を意図しているのでしょうか?
「If Not exlExcelApp Is Nothing Then」の内側では、
MRComObject によって、既に Nothing になっているようですし、
If ブロック内を処理していないなら、既に Nothing になっているハズですよね。
> Public Shared Sub MRComObject(Of T As Class)(ByRef objCom As T, Optional ByVal force As Boolean = False)
どこかで見たコード…。
魔界の仮面弁士さん
オブジェクトの使い方に慣れていなくて、単純な指摘までして頂き、ありがとうございます。
インラインにてコメントします。
>> exlExcelBook = exlExcelApp.Workbooks.Open(strDirPath & "\" & Me.lstTargetFile.Items(intFileList))
> exlExcelApp.Workbooks.Open ではなく、
> exlBooks.Open を使うべきかと。せっかく変数に取ってあるのですし。
おっしゃるとおりですね。修正しました。
>> exlExcelApp.Visible = True
>> exlExcelApp.DisplayAlerts = False
> For Each ループの内側で、これらを毎回実行する必要は無い気がします。
了解です。オブジェクト定義後に1度だけ設定するように修正しました。
>> For i = 1 To exlExcelBook.Sheets.Count
> exlExcelBook.Sheets.Count ではなく、
> exlSheets.Count を使うべきかと。
了解です。修正しました。
>> exlExcelSheet = exlExcelBook.Sheets(i)
> exlExcelBook.Sheets(i) ではなく、
> exlSheets(i) を使うべきかと。
了解です。修正しました。
>> Dim exlCells As Excel.Range = exlExcelSheet.Cells
>> For iRows = intEmployeeDataFromRows To intEmployeeDataToRows
>> '全シートのexlCellsに対する参照処理
>> Next
> この中で、ReleaseComObject が行われていない場所はありませんか?
exlCellsでセルのvalueを参照して値の抽出処理を行っているだけでReleaseComObjectは行っておりません。
>> MRComObject(exlCells)
>> exlCells = Nothing
>変数への Nothing 代入は、既に MRComObject 内で行われているようですが、
>それだけでは不足だったのでしょうか?
不足ではないですが、ウォッチリストで変数を見ると何やら残骸が残っている
ように見えていたため、Nothingして綺麗にしていました。
不要ということで削除しました。
>> If Not exlExcelBook Is Nothing Then
>この時点で、exlExcelBook が Nothing になる事は無いように思えます。
了解です。削除しました。
>> If Not exlExcelBook Is Nothing Then
>> Try
>> exlExcelBook.Close(False)
>> Finally
>> MRComObject(exlExcelBook)
>> End Try
>> End If
>> exlExcelBook = Nothing
>> MRComObject(exlSheets)
>> exlSheets = Nothing
>> MRComObject(exlBooks)
>> exlBooks = Nothing
>Range → Sheet → Workbook → Sheets → Workbooks の順に解放していますが、
>念のため、下位のオブジェクトから行っていった方が良いと思います。
>Range → Sheet → Sheets → Workbook → Workbooks の順に書き換えてみてください。
了解です。書き換えました。
>> exlExcelApp.Quit()
>> intFileCnt = intFileCnt + 1
>> Next
>> 'Excelの結果に応じて、画面のListViewの編集処理
>> If Not exlExcelApp Is Nothing Then
>> Try
>> exlExcelApp.Quit()
>ループ内の exlExcelApp.Quit は問題があるように思えます。
>Quit するなら、ReleaseComObject もセットで行うべきですし、
>Quit した後で、2回目以降に exlExcelApp を使い続けるべきではありません。
なるほど。これはちょっと試してみたいと思います。
例えば、exlExcelAppをオブジェクト型で定義して、その変数をExcelファイル毎に
ReleaseComObjectの解放とReleaseComObjectのセット(?)でやっていくということでしょうか?
>> If Not exlExcelApp Is Nothing Then
>> Try
>> exlExcelApp.Quit()
>> Finally
>> MRComObject(exlExcelApp)
>> End Try
>> End If
>> exlExcelApp = Nothing
>最後の Nothing 代入は、何を意図しているのでしょうか?
>
>「If Not exlExcelApp Is Nothing Then」の内側では、
>MRComObject によって、既に Nothing になっているようですし、
>If ブロック内を処理していないなら、既に Nothing になっているハズですよね。
はい。前述したとおり、Nothingは削除するようにします。
>> Public Shared Sub MRComObject(Of T As Class)(ByRef objCom As T, Optional ByVal force As Boolean = False)
>どこかで見たコード…。
VBレスキュー(花ちゃん)の方で参考にさせて頂きました・・・。
> exlCellsでセルのvalueを参照して値の抽出処理を行っているだけでReleaseComObjectは行っておりません。
それはどのようなコードですか?
たとえば、
data = exlCells(行, 列).Value
などの記述は NG ですが、その点は大丈夫でしょうか。
>> Quit するなら、ReleaseComObject もセットで行うべきですし、
>> Quit した後で、2回目以降に exlExcelApp を使い続けるべきではありません。
> なるほど。これはちょっと試してみたいと思います。
Quit は、Excel アプリ本体を終了させるためのメソッドですよね。
(Form に対する Close メソッドのようなもの)
ループ終了後に閉じている部分に関しては分かりますが、ループ中で
(ブックでは無く)Excel 本体を毎回終了させているのは、あまり
意味が無い様に思えました。
終了させる必要が無いのであれば、Quit すべきでは無いと思いますし、
終了させる必要があるのなら、Quit 後には新たに Excel.Application を
起動しなおすべきでしょう。
> 例えば、exlExcelAppをオブジェクト型で定義して、その変数をExcelファイル毎に
> ReleaseComObjectの解放とReleaseComObjectのセット(?)でやっていくということでしょうか?
ごめんなさい。質問の意味が分かりませんでした。
急に「オブジェクト型」という言葉が出てきたので困惑していますが、
それは As Object で宣言するという意味でしょうか。もしも現状の
Dim exlExcelApp As New Excel.Application()
というコードを
Dim exlExcelApp As Object = New Excel.Application()
にするという意味だとしたら、そのような事はすべきではありません。
また、「ReleaseComObjectの解放とReleaseComObjectのセット」という文も
何を意味しているのか分かりませんでした。『(?)』と書かれていますので、
御自身でも内容を伝え切れていないのだとは想像しますが…。
# ReleaseComObjectの解放というのは分かりますが、
# ReleaseComObjectのセットとは一体?
で、解放処理についてですが、それぞれをどのタイミングで行うべきかは
事前に考えておいてください。
たとえば、今回のコードは複数のファイルを順に読み出しているようですが、
(案1) Excel をひとつだけ起動し、その中で、ファイルを順次加工していく。
(案2) ファイルを編集するたびに Excel 自体も再起動する。
のいずれかで、解放のタイミングは異なってくるかと思います。
案1で実装する場合は、このような流れになるでしょう。
Excel の起動および解放は、ListBox のループよりも外側で行います。
Excel本体 起動
Workbooks 取得
≫ループ開始[ファイル名]
Workbook 取得
Sheets 取得
≫ループ開始[Sheet番号]
Sheet 取得
Range 取得
(各セルの内容を読み取る)
Range 解放
Sheet 解放
≪ループ終了[Sheet番号]
Sheets 解放
Workbook.Close
Workbook 解放
≪ループ終了[ファイル名]
Workbooks 解放
Excel本体.Quit
Excel本体 解放
案2 の場合は、こんな感じ。
Excel の起動および解放が、ListBox のループの内側で行われます。
≫ループ開始[ファイル名]
Excel本体 起動
Workbooks 取得
Workbook 取得
Sheets 取得
≫ループ開始[Sheet番号]
Sheet 取得
Range 取得
(各セルの内容を読み取る)
Range 解放
Sheet 解放
≪ループ終了[Sheet番号]
Sheets 解放
Workbook.Close
Workbook 解放
Workbooks 解放
Excel本体.Quit
Excel本体 解放
≪ループ終了[ファイル名]
>>> Public Shared Sub MRComObject(Of T As Class)(ByRef objCom As T, Optional ByVal force As Boolean = False)
>>どこかで見たコード…。
> VBレスキュー(花ちゃん)の方で参考にさせて頂きました・・・。
http://hanatyan.sakura.ne.jp/dotnet/Excel08.htm あたりでしょうか。
(あちらにも掲示板はありますよね?)
なお、無断転載(≠引用)は禁止されていますのでご注意あれ。
http://hanatyan.sakura.ne.jp/hazimeni.htm
魔界の仮面弁士さん
分かりやす説明と丁寧なご返信をありがとうございます。
インラインにて、解答させて頂きます。
>> exlCellsでセルのvalueを参照して値の抽出処理を行っているだけで ReleaseComObjectは行っておりません。
>それはどのようなコードですか?
>
>たとえば、
> data = exlCells(行, 列).Value
>などの記述は NG ですが、その点は大丈夫でしょうか。
そうなんですね…。NGがありました。
Valueで設定しているだけでもその変数の解放が必要なんですね。
例でいう、MRComObject(data)を追加しますね。
>>> Quit するなら、ReleaseComObject もセットで行うべきですし、
>>> Quit した後で、2回目以降に exlExcelApp を使い続けるべきではありません。
>> なるほど。これはちょっと試してみたいと思います。
>
>Quit は、Excel アプリ本体を終了させるためのメソッドですよね。
>(Form に対する Close メソッドのようなもの)
>
>ループ終了後に閉じている部分に関しては分かりますが、ループ中で
>(ブックでは無く)Excel 本体を毎回終了させているのは、あまり
>意味が無い様に思えました。
>
>終了させる必要が無いのであれば、Quit すべきでは無いと思いますし、
>終了させる必要があるのなら、Quit 後には新たに Excel.Application を
>起動しなおすべきでしょう。
やりたい事は案1の方でした。
確かにおっしゃるとおりQuitは必要ないですね。削除します。
>> 例えば、exlExcelAppをオブジェクト型で定義して、その変数をExcelファイル毎に
>> ReleaseComObjectの解放とReleaseComObjectのセット(?)でやっていくということでしょうか?
>
>ごめんなさい。質問の意味が分かりませんでした。
>
>急に「オブジェクト型」という言葉が出てきたので困惑していますが、
>それは As Object で宣言するという意味でしょうか。もしも現状の
> Dim exlExcelApp As New Excel.Application()
>というコードを
> Dim exlExcelApp As Object = New Excel.Application()
>にするという意味だとしたら、そのような事はすべきではありません。
>
>また、「ReleaseComObjectの解放とReleaseComObjectのセット」という文も
>何を意味しているのか分かりませんでした。『(?)』と書かれていますので、
>御自身でも内容を伝え切れていないのだとは想像しますが…。
>
># ReleaseComObjectの解放というのは分かりますが、
># ReleaseComObjectのセットとは一体?
こちらこそ、困惑させてしまい、申し訳ありません。
オブジェクト型は「As Object」の意味でして、以下のように
魔界の仮面弁士がおっしゃる案2のイメージでした。
Dim exlExcelApp As Object
[ループ1 開始]
exlExcelApp = new Excel.Application() ←こことReleaseComObjectのセットと勘違い
exlbook = exlExcelApp.books.open("")
[ループ2 開始]
[ループ2 終了]
exlbook 解放
exlExcelApp 解放
[ループ1 終了]
>
>で、解放処理についてですが、それぞれをどのタイミングで行うべきかは
>事前に考えておいてください。
>
>たとえば、今回のコードは複数のファイルを順に読み出しているようですが、
> (案1) Excel をひとつだけ起動し、その中で、ファイルを順次加工していく。
> (案2) ファイルを編集するたびに Excel 自体も再起動する。
>のいずれかで、解放のタイミングは異なってくるかと思います。
>
>案1で実装する場合は、このような流れになるでしょう。
>Excel の起動および解放は、ListBox のループよりも外側で行います。
>
> Excel本体 起動
> Workbooks 取得
> ≫ループ開始[ファイル名]
> Workbook 取得
> Sheets 取得
> ≫ループ開始[Sheet番号]
> Sheet 取得
> Range 取得
> (各セルの内容を読み取る)
> Range 解放
> Sheet 解放
> ≪ループ終了[Sheet番号]
> Sheets 解放
> Workbook.Close
> Workbook 解放
> ≪ループ終了[ファイル名]
> Workbooks 解放
> Excel本体.Quit
> Excel本体 解放
>
>案2 の場合は、こんな感じ。
>Excel の起動および解放が、ListBox のループの内側で行われます。
>
> ≫ループ開始[ファイル名]
> Excel本体 起動
> Workbooks 取得
> Workbook 取得
> Sheets 取得
> ≫ループ開始[Sheet番号]
> Sheet 取得
> Range 取得
> (各セルの内容を読み取る)
> Range 解放
> Sheet 解放
> ≪ループ終了[Sheet番号]
> Sheets 解放
> Workbook.Close
> Workbook 解放
> Workbooks 解放
> Excel本体.Quit
> Excel本体 解放
> ≪ループ終了[ファイル名]
>
案1の方が処理的に軽そうなので、そちらで試してみます。
ありがとうございました。
>>>> Public Shared Sub MRComObject(Of T As Class)(ByRef objCom As T, Optional ByVal force As Boolean = False)
>>>どこかで見たコード…。
>> VBレスキュー(花ちゃん)の方で参考にさせて頂きました・・・。
>http://hanatyan.sakura.ne.jp/dotnet/Excel08.htm あたりでしょうか。
>(あちらにも掲示板はありますよね?)
ありますが、ここの掲示板の情報を見てた時に質問したくなったので・・・。
かなり場当たりです^^;
>
>なお、無断転載(≠引用)は禁止されていますのでご注意あれ。
>http://hanatyan.sakura.ne.jp/hazimeni.htm
個人利用なので、いいかなと思っていましたが、断りを入れるようにします。
ありがとうございました。
また、ちゃんと解放まで出来るようになったら、報告しますね。
魔界の仮面弁士さん
ご指摘の内容を適用したところ、プロセスにExcel.exeが残らないようになりました!
ありがとうございます!
解決チェックを忘れていました。
魔界の仮面弁士さん、ありがとうございます。
>> data = exlCells(行, 列).Value
>>などの記述は NG ですが、その点は大丈夫でしょうか。
> そうなんですね…。NGがありました。
先の回答で、「exlExcelSheet = exlExcelBook.Sheets(i)」ではなく
「exlExcelSheet = exlSheets(i)」の形式を使うように指摘していますが、
それとこれは、いずれも同種の理由によるものです。
exlCells(行, 列) は、exlCells.Default(行, 列) の省略表記です。
そして、exlCells.Default(行, 列) は、Range オブジェクトを返しますから、
この Range オブジェクトも解放すべき対象となります。ゆえに、
foo = exlCells(行, 列)
data = foo.Value
Marshal.ReleaseComObject(foo)
が求められます。
> オブジェクト型は「As Object」の意味でして、
それは、避けておいた方が無難かと思います。
As Object な変数を使って操作すると、特定の条件化においては、
内部的な型変換のタイミングで参照カウントが増加してしまい、
解放処理を複雑にしてしまうケースが存在します。
例えば、
Dim Hs As Excel.HPageBreaks = objSheet1.HPageBreaks
Dim H As Excel.HPageBreak = Hs.Add(objRange)
Marshal.ReleaseComObject(H)
Marshal.ReleaseComObject(Hs)
Marshal.ReleaseComObject(objRange)
Marshal.ReleaseComObject(objSheet1)
ならば正しく解放されたコードが、参照設定無しだと
Dim Hs As Object = objSheet1.HPageBreaks
Dim H As Object = Hs.Add(objRange) 'ここで参照カウントが増加してしまう。
Marshal.ReleaseComObject(H)
Marshal.ReleaseComObject(Hs)
Marshal.ReleaseComObject(objRange) 'そのため、ここで解放してもまだ一つ残っている…。
Marshal.ReleaseComObject(objSheet1)
という結果になってしまった事がありました。
http://madia.world.coocan.jp/cgi-bin/VBBBS2/wwwlng.cgi?print+200512/05120042.txt
魔界の仮面弁士さん
返事が遅くなりました。
>>> data = exlCells(行, 列).Value
>>>などの記述は NG ですが、その点は大丈夫でしょうか。
>> そうなんですね…。NGがありました。
>先の回答で、「exlExcelSheet = exlExcelBook.Sheets(i)」ではなく
>「exlExcelSheet = exlSheets(i)」の形式を使うように指摘していますが、
>それとこれは、いずれも同種の理由によるものです。
>
>exlCells(行, 列) は、exlCells.Default(行, 列) の省略表記です。
>そして、exlCells.Default(行, 列) は、Range オブジェクトを返しますから、
>この Range オブジェクトも解放すべき対象となります。ゆえに、
> foo = exlCells(行, 列)
> data = foo.Value
> Marshal.ReleaseComObject(foo)
>が求められます。
なるほど。了解です。
> オブジェクト型は「As Object」の意味でして、
>それは、避けておいた方が無難かと思います。
>
>As Object な変数を使って操作すると、特定の条件化においては、
>内部的な型変換のタイミングで参照カウントが増加してしまい、
>解放処理を複雑にしてしまうケースが存在します。
>
>例えば、
> Dim Hs As Excel.HPageBreaks = objSheet1.HPageBreaks
> Dim H As Excel.HPageBreak = Hs.Add(objRange)
> Marshal.ReleaseComObject(H)
> Marshal.ReleaseComObject(Hs)
> Marshal.ReleaseComObject(objRange)
> Marshal.ReleaseComObject(objSheet1)
>ならば正しく解放されたコードが、参照設定無しだと
> Dim Hs As Object = objSheet1.HPageBreaks
> Dim H As Object = Hs.Add(objRange) 'ここで参照カウントが増加してしまう。
> Marshal.ReleaseComObject(H)
> Marshal.ReleaseComObject(Hs)
> Marshal.ReleaseComObject(objRange) 'そのため、ここで解放してもまだ一つ残っている…。
> Marshal.ReleaseComObject(objSheet1)
>という結果になってしまった事がありました。
参照カウント増加を招くとは、Excelオブジェクトの解放も奥が深いですね。
まぁ、使った元をちゃんと元の場所に戻すのは当たり前ですけど、
正直な気持ちは魔法を使って一気に元の場所へ戻すように上位層オブジェクト
を解放するだけで下位層オブジェクトも解放してもらいたいですけど(笑)
とにかく、ありがとうございました!