- PR -

Vb.netでExcelが終了しない

投稿者投稿内容
としちゃん
会議室デビュー日: 2004/08/14
投稿数: 3
投稿日時: 2004-08-14 19:41
はじめまして。よろしくお願いします。
以前、http://www.atmarkit.co.jp/bbs/phpBB/viewtopic.php?topic=3895&forum=7
でExcelへの書き込みが増えるとExcelのプロセスが終了しないという書き込みがありましたが、同様の事象がVB.netでもありました。

コードを下記に明記します。
Private Sub a_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles a.Click

Dim intRowPos As Integer

Dim strFileName as Strimg = "C:\a.xls"


Dim blnRet As Boolean

Dim objExcel As New Excel.Application
Dim objBooks As New Excel.Workbooks
Dim objBook As New Excel.Workbook
Dim objSheets As New Excel.Sheets
Dim objSheet As New Excel.Worksheet
Dim objCell As Excel.Range

objExcel = CreateObject("Excel.Application")
objBooks = Nothing
objBook = Nothing
objSheets = Nothing
objSheet = Nothing

Try

If Dir$(strFileName) = "" Then
objExcel.Workbooks.Add()
Else
objBooks = objExcel.Workbooks
objBook = objBooks.Open(strFileName)
End If

objSheets = objBook.Sheets
objSheet = objSheets(1)
objCell = objSheet.Cells

objCell.Clear()

''行の開始位置を設定
intRowPos = 1

Do
if inrRowPos = 10001 Then
Exit Do
End if

objCell(intRowPos, 1) = 1
objCell(intRowPos, 2) = 2
objCell(intRowPos, 3) = 3
objCell(intRowPos, 4) = 4
objCell(intRowPos, 5) = 5
objCell(intRowPos, 6) = 6
objCell(intRowPos, 7) = 7
objCell(intRowPos, = 8
objCell(intRowPos, 9) = 9
objCell(intRowPos, 10) = 10
objCell(intRowPos, 11) = 11
objCell(intRowPos, 12) = 12
objCell(intRowPos, 13) = 13
objCell(intRowPos, 14) = 14
objCell(intRowPos, 15) = 15
objCell(intRowPos, 16) = 16
objCell(intRowPos, 17) = 17
objCell(intRowPos, 18) = 18
objCell(intRowPos, 19) = 19
objCell(intRowPos, 20) = 20


intRowPos = intRowPos + 1
Loop


objExcel.DisplayAlerts = False

objBook.SaveAs(strFileName, Excel.XlFileFormat.xlExcel9795)

objExcel.DisplayAlerts = True

Catch ex As Exception

''Excel操作によるエラー処理
MsgBox(Err.Description)
Return

End Try

If (IsNothing(objCell) = False) Then
''ランタイム呼び出し可能ラッパーの参照カウントをデクリメントする
System.Runtime.InteropServices.Marshal.ReleaseComObject(objCell)
objCell = Nothing
End If

If (IsNothing(objSheet) = False) Then
''ランタイム呼び出し可能ラッパーの参照カウントをデクリメントする
System.Runtime.InteropServices.Marshal.ReleaseComObject(objSheet)
objSheet = Nothing
End If

If (IsNothing(objSheets) = False) Then
''ランタイム呼び出し可能ラッパーの参照カウントをデクリメントする
System.Runtime.InteropServices.Marshal.ReleaseComObject(objSheets)
objSheets = Nothing
End If


If (IsNothing(objBook) = False) Then
''保存したExcelシートのクローズ
objBook.Close(False)

''ランタイム呼び出し可能ラッパーの参照カウントをデクリメントする
System.Runtime.InteropServices.Marshal.ReleaseComObject(objBook)
objBook = Nothing
End If

If (IsNothing(objBooks) = False) Then
''ランタイム呼び出し可能ラッパーの参照カウントをデクリメントする
System.Runtime.InteropServices.Marshal.ReleaseComObject(objBooks)
objBooks = Nothing
End If

'Excelを閉じる
objExcel.Quit()

'Excelオブジェクトの解放
If Not objExcel Is Nothing Then
''ランタイム呼び出し可能ラッパーの参照カウントをデクリメントする
System.Runtime.InteropServices.Marshal.ReleaseComObject(objExcel)
objExcel = Nothing
End If

'ガベージコレクションのメモリの解放
System.GC.Collect()

'DoEvents を発行
System.Windows.Forms.Application.DoEvents()

Return

End Sub

どなたか解決方法をご伝授いただけませんでしょうか?
よろしくお願いします。

ちなみに、開発環境は、Windows2000/Excel2000/Vb.net2003です。
yayadon
常連さん
会議室デビュー日: 2003/07/23
投稿数: 41
投稿日時: 2004-08-15 12:46
Dim objExcel As New Excel.Application
objExcel = CreateObject("Excel.Application")

は,

Dim objExcel As Excel.Application
objExcel = New Excel.Application <----- (1)
objExcel = CreateObject("Excel.Application") <----- (2)

のようになるわけだから,(1) と (2) のふたつのインスタンスを作成しています。
この場合,(1)のインスタンス(参照カウント)を開放できなくなってしまいますよね?

なので,New を取って,

Dim objExcel As Excel.Application
objExcel = CreateObject("Excel.Application")

です。

また,これが実際の原因でないとしても,
他でも同様で,
New を宣言時に付けると "既定のExcelのインスタンス" ができてしまうので,

Dim objExcel As New Excel.Application
Dim objBooks As New Excel.Workbooks
Dim objBook As New Excel.Workbook
Dim objSheets As New Excel.Sheets
Dim objSheet As New Excel.Worksheet

でなく

Dim objExcel As Excel.Application
Dim objBooks As Excel.Workbooks
Dim objBook As Excel.Workbook
Dim objSheets As Excel.Sheets
Dim objSheet As Excel.Worksheet

の方がいいですよね?
というか,コードを見た限り,Newする必要はないです。

で,実際の開放は,Excelのアプリだけでいいハズです。
Jitta
ぬし
会議室デビュー日: 2002/07/05
投稿数: 6267
お住まい・勤務地: 兵庫県・海手
投稿日時: 2004-08-15 12:54
引用:

稍丼さんの書き込み (2004-08-15 12:46) より:

で,実際の開放は,Excelのアプリだけでいいハズです。


いえ、ブックやシートも解放の必要があります。

AI-Lightというサイトで、最近「Excelの事でお聞きします」という質問がありました。こちらも参照してみてください。
yayadon
常連さん
会議室デビュー日: 2003/07/23
投稿数: 41
投稿日時: 2004-08-17 06:49
> いえ、ブックやシートも解放の必要があります。

確かにそのようなんですが...

でも,例えば,横着して,

Dim app As Excel.Application

app = New Excel.ApplicationClass
app.Workbooks.Add()

For i As Integer = 1 To 100
app.Workbooks(1).Worksheets(1).Cells(i, 1).Formula _
= "Hoge" & CStr(i)
Next

app.Workbooks(1).Close(False)
app.Quit()
System.Runtime.InteropServices. _
Marshal.ReleaseComObject(app)
app = Nothing

GC.Collect()

MessageBox.Show("終了")

でも,残らないんですよね...

理屈的には,上位オブジェクトさえちゃんと開放すれば,
下位オブジェクトは開放されないとおかしいじゃないかな
という気がするのですが...
yayadon
常連さん
会議室デビュー日: 2003/07/23
投稿数: 41
投稿日時: 2004-08-17 07:06
今回の件ですが,宣言時に New してしまうと,

Dim objBooks As New Excel.Workbooks



Dim objBooks As Excel.Workbooks
objBooks = New Excel.Workbooks

の意味になり,オートメーションなのに,
Applicationオブジェクトから参照をたどっていないので
既定のExcelのインスタンスができてしまっていて
それが開放されないのが主な原因のような気がします...
渋木宏明(ひどり)
ぬし
会議室デビュー日: 2004/01/14
投稿数: 1155
お住まい・勤務地: 東京
投稿日時: 2004-08-17 08:21
引用:

理屈的には,上位オブジェクトさえちゃんと開放すれば,
下位オブジェクトは開放されないとおかしいじゃないかな
という気がするのですが...



そう単純な話でもないです。
COM ランタイムはオブジェクトの寿命を参照カウンタベースで管理しますが、現行の .NET の COM Interrop は参照カウンタを真面目に管理していません。
なので、比較的 COM 仕様に素直に従っている Office アプリケーションでは、「下位オブジェクトが居残っているが故に、Quit() してもサーバが終了しない」という事態が発生してしまいます。
IE なんかは、下位オブジェクトの参照が残っていても、Quit() で有無を言わさずサーバが終了するつくりになっています。


_________________
// 渋木宏明 (Hiroaki SHIBUKI)
// http://hidori.jp/
// Microsoft MVP for Visual C#
//
// @IT会議室 RSS 配信中: http://hidori.jp/rss/atmarkIT/
としちゃん
会議室デビュー日: 2004/08/14
投稿数: 3
投稿日時: 2004-08-17 18:40
みなさん、いろいろとありがとうございました。
また、返事が遅くなりまして申し訳ございません。

実際のところVB6.0までは
Dim objExcel As Excel.Application

objExcel = CreateObject("Excel.Application")
objExcel.WorkBooks(1).Sheets(1).Cells(1,1).Value = 100

objExcel.Quit()
Set objExcel =Nothing

と記述すれば、Excelのプロセスは解放されていたみたいですが、VB.NETだと、「WorkBooks」
「Sheets」「Cells」それぞれに対してのオブジェクトが「objExcel」にひもづくオブジェ
クトとして、自動的に生成し確保されているみたいです。
(暗黙の了解で作成されているため、見た目では判断つかないそうです。)

そのため、ガベージコレクションで、「Excel.Application」オブジェクトを解放しようとし
た際、暗黙的で作られた、「WorkBooks」「Sheets」「Cells」の各オブジェクトは「暗黙
的なオブジェクト=必要なオブジェクト」と判断されるようです。すなわち、「明らかに不
要であるオブジェクト」と判断できないようです。その結果、親である「Excel.Application」
オブジェクトも解放されずに、Excelのプロセスが残ってしまってたみたいですね。

すなわち
Dim objExcel As Excel.Application
Dim objBooks As Excel.Workbooks
Dim objBook As Excel.Workbook
Dim objSheets As Excel.Sheets
Dim objSheet As Excel.Worksheet
Dim objCells As Excel.Range
Dim objCell As Excel.Range

objExcel = CreateObject("Excel.Application")
objBooks = Nothing
objBook = Nothing
objSheets = Nothing
objSheet = Nothing
objCells = Nothing
objCell = Nothing

objBooks = objExcel.Workbooks
objBook = objBooks.Open(strFileName)
objSheets = objBook.Sheets
objSheet = objSheets(1)
objCells = objSheet.Cells

objCell = objSheet.Cells(1, 1)
objCell.Value = 100

System.Runtime.InteropServices.Marshal.ReleaseComObject(objCell)
objCell = Nothing

System.Runtime.InteropServices.Marshal.ReleaseComObject(objCells)
objCells = Nothing

System.Runtime.InteropServices.Marshal.ReleaseComObject(objSheet)
objSheet = Nothing

System.Runtime.InteropServices.Marshal.ReleaseComObject(objSheets)
objSheet = Nothing

objBook.Close(False)
System.Runtime.InteropServices.Marshal.ReleaseComObject(objBook)
objBook = Nothing


System.Runtime.InteropServices.Marshal.ReleaseComObject(objBooks)
objBooks = Nothing

objExcel.Quit()

System.Runtime.InteropServices.Marshal.ReleaseComObject(objExcel)
objExcel = Nothing

System.GC.Collect()

System.Windows.Forms.Application.DoEvents()

といった形で、ワークシート・ブック・セルそれぞれに対して明確的なオブジェクトを参照
して明確なオブジェクトを解放するという手順を踏まえて、行うとプロセスが消えたみた
いです。

また、画面を使ってExcelの操作を行う場合は、実際の解放処理は画面に制御が戻った時な
ので、「DoEvents()」を用いて処理を行ったほうが、確実にプロセスが解放できるみたいです。

稍丼さん・Jittaさん・渋木宏明(ひどり)さん、ありがとうございました。
yayadon
常連さん
会議室デビュー日: 2003/07/23
投稿数: 41
投稿日時: 2004-08-18 00:57
横着してもちゃんと消えた環境を書いておきます。

WinXP SP1 & Excel 2002 SP3 & VB.NET 2003

で確認しています。なんとなくですが,
Excelのバージョンよっても微妙に違うということも
なきにしもあらずという気もするんですが...

昨日からいろいろなパターンで検証しましたが,やはり,
上記の環境だと,Excelのインスタンスが残らないようにするには,
VB.NET側で,変数でうけたものは,obj = Nothing は必要ですが,
途中利用?する個別のオブジェクトをわざわざ ReleaseComObject(...) する必要はなく,
GC.Collect() の方が必須条件になっています。

スキルアップ/キャリアアップ(JOB@IT)