C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

Re[8]: VB.NETでエクセルファイル操作


(過去ログ 91 を表示中)

[トピック内 12 記事 (1 - 12 表示)]  << 0 >>

■54129 / inTopicNo.1)  VB.NETでエクセルファイル操作
  
□投稿者/ おじゃ (1回)-(2010/10/06(Wed) 00:14:49)

分類:[VB.NET/VB2005 以降] 

VB.NET2005を使用しています。エクセルは、2002です。

VB.NETから、既に開かれているエクセルファイルをオブジェクトとして取得し、
ブックを保存してエクセルを閉じたいのですが、
閉じたあと、プロセスからExcel.exeが消えません。
プログラムを終了すれば消えるのですが、常時接続なので、終了できません。

ご存じの方がいらっしゃいましたら教えてください。よろしく願いいたします。

引用返信 編集キー/
■54130 / inTopicNo.2)  Re[1]: VB.NETでエクセルファイル操作
□投稿者/ やじゅ (1758回)-(2010/10/06(Wed) 07:15:30)
やじゅ さんの Web サイト
No54129 (おじゃ さん) に返信
> VB.NETから、既に開かれているエクセルファイルをオブジェクトとして取得し、
> ブックを保存してエクセルを閉じたいのですが、
> 閉じたあと、プロセスからExcel.exeが消えません。

「Excel プロセス 消えない」でGoogleで検索するとたくさん出てきますが、
その対処方法(解放漏れ)では駄目ですか?

引用返信 編集キー/
■54132 / inTopicNo.3)  Re[2]: VB.NETでエクセルファイル操作
□投稿者/ おじゃ (3回)-(2010/10/06(Wed) 09:28:06)
2010/10/06(Wed) 09:35:35 編集(投稿者)
2010/10/06(Wed) 09:31:24 編集(投稿者)
2010/10/06(Wed) 09:30:39 編集(投稿者)

> 「Excel プロセス 消えない」でGoogleで検索するとたくさん出てきますが、
> その対処方法(解放漏れ)では駄目ですか?

返信ありがとうございます。
「Excel プロセス 消えない」の心当たりのものは全てためしてみたのですが、だめでした。

以下の手順で、プログラム自身がエクセルファイルを開いて閉じた場合は、プロセスは残らないのですが、
既に開いてあったファイルを「オブジェクトを取得」して閉じた場合に、残ってしまいます。

プロセスは残らない
   Dim MyExcel As Excel.Application = New Excel.Application
   Dim Mybooks As Excel.Workbooks = MyExcel.Workbooks
   Dim Mybook As Excel.Workbook = Mybooks.Open(stFilePath) ----開く処理
   System.Runtime.InteropServices.Marshal.ReleaseComObject(Mybooks)
   Mybook.Activate()
   Mybook.Close() ----ブックを閉じる処理
   System.Runtime.InteropServices.Marshal.ReleaseComObject(Mybook)
   MyExcel.Quit() ----ウィンドを閉じる処理
   System.Runtime.InteropServices.Marshal.ReleaseComObject(MyExcel) ----プロセスが消える

プロセスが残る
   Dim MyExcel As Excel.Application = GetObject(ファイル名).Application  ----ファイルを設定する処理
   Dim Mybooks As Excel.Workbooks = MyExcel.Workbooks
   Dim Mybook As Excel.Workbook
   For Each Mybook In Mybooks ----ブックを確定する処理
    If Mybook.Path = フォルダパス Then
     If Mybook.Name = ファイル名 Then
       フラグ = True
       Exit For
     End If
    End If
    If フラグ= False Then
     System.Runtime.InteropServices.Marshal.ReleaseComObject(Mybook) ----1ループごと開放
    End If
   Next
   System.Runtime.InteropServices.Marshal.ReleaseComObject(Mybooks)
   If フラグ = True Then
    Mybook.Activate()
    Mybook.Close() ----ブックを閉じる処理
    System.Runtime.InteropServices.Marshal.ReleaseComObject(Mybook)
    MyExcel.Quit() ----ウィンドを閉じる処理
    System.Runtime.InteropServices.Marshal.ReleaseComObject(MyExcel) ----プロセスが消えない
   Else
    Mybooks,Mybook,MyExcelを開放(それぞれの変数がNothingでない場合のみ開放処理)
   End If
引用返信 編集キー/
■54138 / inTopicNo.4)  Re[3]: VB.NETでエクセルファイル操作
□投稿者/ おじゃ (4回)-(2010/10/06(Wed) 11:53:13)
自己解決しました。

ガベージコレクションを追加したところ、プロセスから開放されました。
これをしないと開放されないということは、やはりコードが悪かったのか、
それとも、開放されるまでの時間が長かっただけなのかわかりませんが、
とりあえず解決といたします。

やじゅさん、回答いただいてありがとうございました。
解決済み
引用返信 編集キー/
■54140 / inTopicNo.5)  Re[3]: VB.NETでエクセルファイル操作
□投稿者/ 魔界の仮面弁士 (1855回)-(2010/10/06(Wed) 12:33:56)
No54132 (おじゃ さん) に返信
>    For Each Mybook In Mybooks ----ブックを確定する処理
IEnumVARIANT が原因という事はありませんか?
For Each ではなく、For ループと Count/Item プロパティを使ってみては如何でしょう。
http://www.divakk.co.jp/aoyagi/csharp_tips_vssenum.html
引用返信 編集キー/
■54141 / inTopicNo.6)  Re[4]: VB.NETでエクセルファイル操作
□投稿者/ ちゃっぴ (43回)-(2010/10/06(Wed) 13:04:35)
そもそも、GetObject で取得した Excel.Workbook 解放していないじゃん。
引用返信 編集キー/
■54142 / inTopicNo.7)  Re[4]: VB.NETでエクセルファイル操作
□投稿者/ おじゃ (5回)-(2010/10/06(Wed) 15:35:25)
No54140 (魔界の仮面弁士 さん) に返信
> ■No54132 (おじゃ さん) に返信
>>   For Each Mybook In Mybooks ----ブックを確定する処理
> IEnumVARIANT が原因という事はありませんか?
> For Each ではなく、For ループと Count/Item プロパティを使ってみては如何でしょう。
> http://www.divakk.co.jp/aoyagi/csharp_tips_vssenum.html

返信ありがとうございます。

「For Each ではなく、For ループと Count/Item 」ということは、
Forループで Mybooks.Count分ループしてMybooks.Item(i)のフォルダとファイルが対象のものだったら
ブックに設定するという意味でしょうか。
For Each MyBook In で設定しないとなると、どのように設定すればよいですか?
特定のブックをCOMのオブジェクトで変数に設定する方法がわかりません。
よろしくお願いします。
引用返信 編集キー/
■54143 / inTopicNo.8)  Re[5]: VB.NETでエクセルファイル操作
□投稿者/ おじゃ (6回)-(2010/10/06(Wed) 15:41:46)
No54141 (ちゃっぴ さん) に返信
> そもそも、GetObject で取得した Excel.Workbook 解放していないじゃん。

返信ありがとうございます。

GetObject で取得した Excel.Workbook は、対象外のファイルについては、
For Each の最後に、その都度開放しているのと、
対象となったファイルについては、最後に「System.Runtime.InteropServices.Marshal.ReleaseComObject(Mybook)」
で開放しています。
見落としている箇所を具体的に教えていただけますか?
よろしくお願いします。






引用返信 編集キー/
■54144 / inTopicNo.9)  Re[5]: VB.NETでエクセルファイル操作
□投稿者/ 魔界の仮面弁士 (1856回)-(2010/10/06(Wed) 16:10:40)
No54142 (おじゃ さん) に返信
> For Each MyBook In で設定しないとなると、どのように設定すればよいですか?

たとえば、
 For Each objBook In objBooks
  :
 Next
の代わりに、
 For n = 1 To objBooks.Count
  objBook = objBooks.Item(n)
  :
 Next
と記述するという事です(もちろん、RelaseComObject も必要です)。

それと、解放漏れが疑われる場合には、それぞれの RelaseComObject の戻り値を
確認してみてください。= 0 が返されれば問題ありませんが、> 0 の場合には、
どこかで余計に参照カウントが増やされているため、追加で RelaseComObject を
行う必要があります(もしくは、FinalReleaseComObject を利用します)。


No54143 (おじゃ さん) に返信
>>そもそも、GetObject で取得した Excel.Workbook 解放していないじゃん。
> 見落としている箇所を具体的に教えていただけますか?

最初の
 Dim MyExcel As Excel.Application = GetObject(ファイル名).Application
という部分を、
 Dim objWorkbook As Object = GetObject(ファイル名) 'As Workbook
 Dim MyExcel As Excel.Application = obj.Application
 Marshal.FinalRelaseComObject(objWorkbook)
に変更してみては如何でしょうか。試していませんけれども。

なお、最終的に得られた Mybook と上記の objWorkbook が同一インスタンスの場合は、
Mybook を RelaseComObject するだけで、その Workbook への参照カウントが減じられますが、
見た目の参照数が 2 以上になっていた場合、Mybook を 1 回 RelaseComObject しただけでは
不十分な可能性があります。
引用返信 編集キー/
■54145 / inTopicNo.10)  Re[6]: VB.NETでエクセルファイル操作
□投稿者/ おじゃ (7回)-(2010/10/06(Wed) 18:02:35)
No54144 (魔界の仮面弁士 さん) に返信
> ■No54142 (おじゃ さん) に返信
>>For Each MyBook In で設定しないとなると、どのように設定すればよいですか?
>
> たとえば、
>  For Each objBook In objBooks
>   :
>  Next
> の代わりに、
>  For n = 1 To objBooks.Count
>   objBook = objBooks.Item(n)
>   :
>  Next
> と記述するという事です(もちろん、RelaseComObject も必要です)。
>
> それと、解放漏れが疑われる場合には、それぞれの RelaseComObject の戻り値を
> 確認してみてください。= 0 が返されれば問題ありませんが、> 0 の場合には、
> どこかで余計に参照カウントが増やされているため、追加で RelaseComObject を
> 行う必要があります(もしくは、FinalReleaseComObject を利用します)。

正しく動作しました。ありがとうございます!

RelaseComObject の戻り値を確認したところ、>0でした。
原因は、For Eachにあると思うのですが、理由がわかりません。
ForループもFor Eachも、その都度開放しているという点で、同じだと思うのですが
開放の仕方に問題があるのでしょうか。



>
> ■No54143 (おじゃ さん) に返信
> >>そもそも、GetObject で取得した Excel.Workbook 解放していないじゃん。
>>見落としている箇所を具体的に教えていただけますか?
>
> 最初の
>  Dim MyExcel As Excel.Application = GetObject(ファイル名).Application
> という部分を、
>  Dim objWorkbook As Object = GetObject(ファイル名) 'As Workbook
>  Dim MyExcel As Excel.Application = obj.Application
>  Marshal.FinalRelaseComObject(objWorkbook)
> に変更してみては如何でしょうか。試していませんけれども。
>
> なお、最終的に得られた Mybook と上記の objWorkbook が同一インスタンスの場合は、
> Mybook を RelaseComObject するだけで、その Workbook への参照カウントが減じられますが、
> 見た目の参照数が 2 以上になっていた場合、Mybook を 1 回 RelaseComObject しただけでは
> 不十分な可能性があります。

ありがとうございます! この方法でも正しく動作しました。

1つ解らないのですが、
変更前の状態で、参照数が2以上になっていたので0になるまでRelaseComObjectを繰り返してみましたが、
プロセスは残ったままでした。
FinalReleaseComObjectを実行しても結果は同じでした。
これは、GCまでに時間がかかるからという理由からでしょうか。

引用返信 編集キー/
■54149 / inTopicNo.11)  Re[7]: VB.NETでエクセルファイル操作
□投稿者/ 魔界の仮面弁士 (1857回)-(2010/10/06(Wed) 20:06:19)
2010/10/06(Wed) 20:06:49 編集(投稿者)

No54145 (おじゃ さん) に返信
> 原因は、For Eachにあると思うのですが、理由がわかりません。

原因と理由の回答になっているかは分かりませんが、

 For Each objBook In objBooks
  MsgBox(objBook.FullName)
  Marshal.ReleaseComObject(objBook)
 Next

という処理においては、実際には下記に相当する処理が実行されます。

 'Dim objEnum As Object = CallByName(objBooks, "_NewEnum", vbGet)
 'Dim objEnum As Object = CallByName(objBooks, "[DispID=-4]", vbMethod)
 Dim objEnum As IEnumerator = objBooks.GetEnumerator()
 Do While objEnum.MoveNext()
  objBook = objEnum.Current
  MsgBox(objBook.FullName)
  Marshal.ReleaseComObject(objBook)
 Loop

で、このように置き換えたところで、Excel が残ってしまう状況は変わらないのですが、
先に紹介した URL の記事に従い、While 変換したループの後に

 'EnumeratorViewOfEnumVariant が保持している IEnumVARIANT を得る
 Dim o As Object = DirectCast(objEnum, ICustomAdapter).GetUnderlyingObject()
 If Marshal.IsComObject(o) Then 'True のはず
  '解放
  Marshal.ReleaseComObject(o)
 End If

としてみると、EXCEL.EXE が残ってしまう現象はなくなると思います。

ちなみに VB6 においても、For Each 〜 Next を使うと、Excel が正常に解放されない事があったため、
私は極力、For 〜 Next に置き換えるようにしています。



> 変更前の状態で、参照数が2以上になっていたので0になるまでRelaseComObjectを繰り返してみましたが、
> プロセスは残ったままでした。
内部的に利用された何らかの COM Object の参照が
リリースされていないのだと思います。先述の IEnumVARIANT などのように。


> これは、GCまでに時間がかかるからという理由からでしょうか。
COM のメモリ管理と .NET ベースのメモリ管理について。
http://msdn.microsoft.com/ja-jp/library/cc325771.aspx
引用返信 編集キー/
■54160 / inTopicNo.12)  Re[8]: VB.NETでエクセルファイル操作
□投稿者/ おじゃ (8回)-(2010/10/07(Thu) 09:30:04)
No54149 (魔界の仮面弁士 さん) に返信
> 2010/10/06(Wed) 20:06:49 編集(投稿者)

> で、このように置き換えたところで、Excel が残ってしまう状況は変わらないのですが、
> 先に紹介した URL の記事に従い、While 変換したループの後に
>
>  'EnumeratorViewOfEnumVariant が保持している IEnumVARIANT を得る
>  Dim o As Object = DirectCast(objEnum, ICustomAdapter).GetUnderlyingObject()
>  If Marshal.IsComObject(o) Then 'True のはず
>   '解放
>   Marshal.ReleaseComObject(o)
>  End If
>
> としてみると、EXCEL.EXE が残ってしまう現象はなくなると思います。
>
> ちなみに VB6 においても、For Each 〜 Next を使うと、Excel が正常に解放されない事があったため、
> 私は極力、For 〜 Next に置き換えるようにしています。

For Eachはそのような仕組みになっているんですね。「In」は、「Item(i)の代入」と同じだと思っていました。先にリンクいただいた内容も、しっかり読んで理解できるように勉強しようと思いました。


>>変更前の状態で、参照数が2以上になっていたので0になるまでRelaseComObjectを繰り返してみましたが、
>>プロセスは残ったままでした。
> 内部的に利用された何らかの COM Object の参照が
> リリースされていないのだと思います。先述の IEnumVARIANT などのように。

やみくもにカウントを0にしたから解放されるというものではないんですね。


>>これは、GCまでに時間がかかるからという理由からでしょうか。
> COM のメモリ管理と .NET ベースのメモリ管理について。
> http://msdn.microsoft.com/ja-jp/library/cc325771.aspx

教えていただいてありがとうございます。とても助かりました。
リンク先も、きちんと読んで勉強したいと思います。

解決済み
引用返信 編集キー/


トピック内ページ移動 / << 0 >>

このトピックに書きこむ

過去ログには書き込み不可

管理者用

- Child Tree -