- PR -

VB.NETでエクセルエグゼが残ってしまいます。

投稿者投稿内容
エド
常連さん
会議室デビュー日: 2006/12/07
投稿数: 43
投稿日時: 2006-12-18 17:52
色々なサイトで上記の件名を検索して調べたのですが、
分からないので質問いたします。

まず、調べたサイトでの記述と自分の環境の相違について

自分の開発環境では、以前にシステムをリリースした際に不具合が生じたため、
参照設定でエクセルを参照しないで、エクセル出力処理をしています。

よって、
通常
 Dim xlApp As New Excel.Application
 Dim xlBooks As Excel.Workbooks = xlApp.Workbooks
 Dim xlBook As Excel.Workbook = xlBooks.Add
 Dim xlSheets As Excel.Sheets = xlBook.Worksheets
 Dim xlSheet As Excel.Worksheet = xlSheets.Item(1)
と宣言する部分を
 Dim xlApp As New Object
 xlApp = CType(CreateObject("Excel.Application"), Object)
 Dim xlBooks As Object = xlApp.Workbooks
 Dim xlBook As Object = xlBooks.Add
 Dim xlSheets As Object = xlBook.Worksheets
 Dim xlSheet As Object = xlSheets.Item(1)
 Dim xlCells As Object
 Dim xlRange As Object
と宣言しています。
オブジェクト型の変数に無理やりしれたような感じです。

処理を終了してから
'オブジェクトの解放
MRComObject(xlCells)
MRComObject(xlRange)
MRComObject(xlSheet)
MRComObject(xlSheets)
xlBook.Close(False)
MRComObject(xlBook)
MRComObject(xlBooks)
xlApp.Quit()
MRComObject(xlApp)
GC.Collect()

↓(http://www.bcap.co.jp/hanafusa/dotnet/Excelflm.htm参考)
Private Sub MRComObject(ByRef objCom As Object)

'COM オブジェクトの使用後、明示的に COM オブジェクトへの参照を解放する
Try
'提供されたランタイム呼び出し可能ラッパーの参照カウントをデクリメントします
If Not objCom Is Nothing AndAlso System.Runtime.InteropServices. _
Marshal.IsComObject(objCom) Then
Dim I As Integer
Do
I = System.Runtime.InteropServices.Marshal.ReleaseComObject(objCom)
Loop Until I <= 0
End If
Catch
Finally
'参照を解除する
objCom = Nothing
End Try

End Sub
と行っています。

ある処理の場合はエクセルエグゼが残らずに消えるのですが、
もう一つの処理の場合だけ、エグゼが残ってしまいます。

しかし、システムを終了するとエグゼは消えます。

どなたかご教授願います。
じゃんぬねっと
ぬし
会議室デビュー日: 2004/12/22
投稿数: 7811
お住まい・勤務地: 愛知県名古屋市
投稿日時: 2006-12-18 19:25
引用:

エドさんの書き込み (2006-12-18 17:52) より:

(snip)
オブジェクト型の変数に無理やりしれたような感じです。
(snip)
ある処理の場合はエクセルエグゼが残らずに消えるのですが、
もう一つの処理の場合だけ、エグゼが残ってしまいます。


遅延バインディングならではの現象 だと思ったのですが、今回は無理矢理参照カウントをデクリメントしているようで...
もうちょっと詳しい実装を見てみないと、アドバイスできそうにないです。

現象が確認できる、最低限のコード (ミニマム コード) を提示して頂けるのが 1 番です。

引用:

しかし、システムを終了するとエグゼは消えます。


これに関しては、何ら不思議なことはないです。
ところで、"エグゼが消える" ではなく、"プロセスが解放される" と言いましょう。

それと、花ちゃんさんからのサイトに掲載されているコード、非常にまずいですね。

コード:

    Try 
        '提供されたランタイム呼び出し可能ラッパーの参照カウントをデクリメントします 
        If Not objCom Is Nothing AndAlso System.Runtime.InteropServices.Marshal.IsComObject(objCom) Then
            Dim I As Integer
            Do
                I = System.Runtime.InteropServices.Marshal.ReleaseComObject(objCom)
            Loop Until I <= 0
        End If
    Catch
    Finally
        '参照を解除する
        objCom = Nothing
    End Try


これだと、他で使われている場合も強制的に参照カウントをデクリメントされます。
こんな危険な実装をするくらいなら、GC に頼った方がまだマシだと思いますね。

_________________
C# と VB.NET の入門サイト
じゃんぬねっと日誌
エド
常連さん
会議室デビュー日: 2006/12/07
投稿数: 43
投稿日時: 2006-12-19 11:01
ご返答ありがとうございます。

現象が確認出来るコードを掲載します。

'エクセル出力する為の変数定義
Dim xlApp As New Object
xlApp = CType(CreateObject("Excel.Application"), Object)

Dim xlBooks As Object = xlApp.Workbooks
Dim xlBook As Object = xlBooks.Add
Dim xlSheets As Object = xlBook.Worksheets
Dim xlSheet As Object = xlSheets.Item(1)
Dim xlCells As Object
Dim xlRange As Object

Dim vliRowsCount As Integer
Dim vliColCount As Integer
Dim omDataTable As New DataTable

Dim olHairetu(0, omDataTable.Columns.Count - 1)
Dim vlsCol As String

Try

'データセットからデータテーブルの設定
omDataTable = omDataSet.Tables(0)

'Excelを非表示
xlApp.Visible = False

'保存時の問合せのダイアログを非表示に設定
xlApp.DisplayAlerts = False

'シートの削除
xlSheets("Sheet2").delete()
xlSheets("Sheet3").delete()

'セルの設定
xlCells = xlSheet.Cells

'***************************************************************************
'↓以下の処理を行う場合(起動判定が"0"以外の場合)、プロセスが解放されません。↓
'***************************************************************************
'起動判別が"0"以外の場合
If vgsKidouHanbetu <> 0 Then

'出力レイアウトの設定("1"の場合フォーマットの設定)
Me.sgUriageSetLayout(xlSheets, xlSheet, vasJigyosyoName, 1)

For vliColCount = 0 To (omDataTable.Columns.Count - 1)

'セル指定
xlRange = xlCells(1, vliColCount + 1)

'出力内容
'項目名の設定
xlRange.value = omDataSet.Tables(0).Columns(vliColCount).ColumnName

Next

ReDim olHairetu(0, omDataTable.Columns.Count - 1)

vlsCol = ":AQ"

For vliRowsCount = 0 To (omDataTable.Rows.Count - 1)

'最大列数分ループ
For vliColCount = 0 To (omDataTable.Columns.Count - 1)

olHairetu(0, vliColCount) = omDataTable.Rows.Item(vliRowsCount).ItemArray(vliColCount)

'***************************************************************************
'↓これは文字化け対策です。必要最低限か迷ったので掲載します。↓
'***************************************************************************

'DBNULLだった場合は処理を抜ける
If IsDBNull(omDataTable.Rows.Item(vliRowsCount).ItemArray(vliColCount)) = False Then

olHairetu(0, vliColCount) = Replace(olHairetu(0, vliColCount), "&#12316;", "〜")
End If
Next

xlSheets("Sheet1").range("A" & vliRowsCount + 2 & vlsCol & vliRowsCount + 2).Value = olHairetu
Next

'列幅の設定
xlSheets("Sheet1").columns("A:AQ").AutoFit()

'シート名の設定
xlSheet.name = vasJigyosyoName

'*********************
'エクセルに保存
'*********************
xlSheet.SaveAs(omSaveFilePass)

'***************************************************************************
'↓以下の処理を行う場合(起動判定が"0"の場合)問題無くプロセスが解放されています。↓
'***************************************************************************

'起動判別が"0"の場合
Else

If vasJigyosyoName = "全社" Then
vlsCol = ":AQ"
'出力レイアウトの設定
Me.sgUriageSetLayout(xlSheets, xlSheet, vasJigyosyoName, 1)
Else
vlsCol = ":AC"
'出力レイアウトの設定
Me.sgUriageSetLayout(xlSheets, xlSheet, vasJigyosyoName, 2)
End If

'項目名の出力
For vliColCount = 0 To (omDataTable.Columns.Count - 1)

'セル指定
xlRange = xlCells(1, vliColCount + 1)

'出力内容
'項目名の設定
xlRange.value = omDataSet.Tables(0).Columns(vliColCount).ColumnName

Next

ReDim olHairetu(0, omDataTable.Columns.Count - 1)

'実データの出力
For vliRowsCount = 0 To (omDataTable.Rows.Count - 1)

'最大列数分ループ
For vliColCount = 0 To (omDataTable.Columns.Count - 1)

olHairetu(0, vliColCount) = omDataTable.Rows.Item(vliRowsCount).ItemArray(vliColCount)

'***************************************************************************
'↓同じく文字化け対策です。必要最低限か迷ったので掲載します。↓
'***************************************************************************

'DBNULLだった場合は処理を抜ける
If IsDBNull(omDataTable.Rows.Item(vliRowsCount).ItemArray(vliColCount)) = False Then

olHairetu(0, vliColCount) = Replace(olHairetu(0, vliColCount), "&#12316;", "〜")
End If
Next

xlSheets("Sheet1").range("A" & vliRowsCount + 2 & vlsCol & vliRowsCount + 2).Value = olHairetu
Next

'列幅の設定
xlSheets("Sheet1").columns("A:AQ").AutoFit()

'シート名の設定
xlSheet.name = vasJigyosyoName

'*********************
'全社フォルダに保存(企画部保管用)
'*********************
If vasJigyosyoName = "全社" Then

'ファイルパス
xlSheet.SaveAs(cgsCsvFileDir & "\" & vasJigyosyoName & "\売上受注\売上受注明細_" & Now.ToString("yyyyMMdd") & "(全社).xls")

'*********************
'各部支店用フォルダに保存
'*********************
ElseIf vasJigyosyoName <> "全社" Then

'ファイルパス
xlSheet.SaveAs(cgsCsvFileDir & "\" & vasJigyosyoName & "\売上受注\売上受注明細_" & Now.ToString("yyyyMMdd") & "(" & vasJigyosyoName & ").xls")
End If
End If
Catch ex As Exception

Return "1131"
Finally

'オブジェクトの解放
MRComObject(xlCells)
MRComObject(xlRange)
MRComObject(xlSheet)
MRComObject(xlSheets)
xlBook.Close(False)
MRComObject(xlBook)
MRComObject(xlBooks)
xlApp.Quit()
MRComObject(xlApp)
GC.Collect()
End Try
End Function

というソースです。
概要はデータセットに格納されたデータを各処理条件(起動判定)によって
異なる形でエクセル出力すると言ったものです。

それと、
引用:
--------------------------------------------------------------------------------
じゃんぬねっとさんの書き込み (2006-12-18 19:25) より:

GC に頼った方がまだマシだと思いますね。

--------------------------------------------------------------------------------
とはどういう事でしょうか?
GC.Collect()
を使うという事でしょうか?
正直に申しますと、「GC.Collect()」 も他のサンプルソースで行っていたので
それをみて記述した状況です。
じゃんぬねっと
ぬし
会議室デビュー日: 2004/12/22
投稿数: 7811
お住まい・勤務地: 愛知県名古屋市
投稿日時: 2006-12-19 11:19
引用:

エドさんの書き込み (2006-12-19 11:01) より:

↓これは文字化け対策です。必要最低限か迷ったので掲載します。↓


"現象が確認できる、最低限のコード (ミニマム コード) を提示して頂けるのが 1 番です" と書きました。
必要最低限かどうかは、試せばわかることだと思いますが...

言い方を変えますと '余分なものは必要ない' です。

引用:

とはどういう事でしょうか?
GC.Collect()
を使うという事でしょうか?
正直に申しますと、「GC.Collect()」 も他のサンプルソースで行っていたので
それをみて記述した状況です。


参照カウントさえ、適切にデクリメントしていれば、GC.Collect メソッドなど必要ないです。
それどころか、GC.Collect メソッドはコストが高いので、無闇に使用すべきでないです。

ただ、先に指摘しました通り、参照カウントが 0 になるように、
無理矢理デクリメントするような実装よりは、遥かにマシだということです。

言い方を変えますと、

コード:

    Do
        I = System.Runtime.InteropServices.Marshal.ReleaseComObject(objCom)
    Loop Until I <= 0


このようなことは、してはいけません。(先のレスで、太字にした部分です)

_________________
C# と VB.NET の入門サイト
じゃんぬねっと日誌
エド
常連さん
会議室デビュー日: 2006/12/07
投稿数: 43
投稿日時: 2006-12-19 11:45
調査した所次のコードを掲載します。

Dim xlApp As New Object
xlApp = CType(CreateObject("Excel.Application"), Object)

Dim xlBooks As Object = xlApp.Workbooks
Dim xlBook As Object = xlBooks.Add
Dim xlSheets As Object = xlBook.Worksheets
Dim xlSheet As Object = xlSheets.Item(1)
Dim xlCells As Object
Dim xlRange As Object

Dim vliRowsCount As Integer
Dim vliColCount As Integer
Dim omDataTable As New DataTable

Dim olHairetu(0, omDataTable.Columns.Count - 1)
Dim vlsCol As String

Try

'データセットからデータテーブルの設定
omDataTable = omDataSet.Tables(0)

'Excelを非表示
xlApp.Visible = False

'保存時の問合せのダイアログを非表示に設定
xlApp.DisplayAlerts = False

'シートの削除
xlSheets("Sheet2").delete()
xlSheets("Sheet3").delete()

'セルの設定
xlCells = xlSheet.Cells

'****************************************************************
'↓これを処理するとプロセスが開放されない状態です
'****************************************************************

'列幅の設定
xlSheets("Sheet1").columns("A:AQ").AutoFit()

'****************************************************************

'シート名の設定
xlSheet.name = vasJigyosyoName

'*********************
'エクセルに保存
'*********************
xlSheet.SaveAs(omSaveFilePass)

'オブジェクトの解放
MRComObject(xlCells)
MRComObject(xlRange)
MRComObject(xlSheet)
MRComObject(xlSheets)
xlBook.Close(False)
MRComObject(xlBook)
MRComObject(xlBooks)
xlApp.Quit()
MRComObject(xlApp)
GC.Collect()


「xlSheets("Sheet1").columns("A:AQ").AutoFit()」
の記述の処理を行わないで実行をした所、無事プロセスは開放されましたが、
上記の処理を行うとプロセスが開放されないと言うところまでわかりました!

エド
常連さん
会議室デビュー日: 2006/12/07
投稿数: 43
投稿日時: 2006-12-19 12:10
解決?しました。

応急処置かも知れませんが、以下の方法でプロセスの開放が出来たので記載します。

下記の
「列幅の設定
xlSheets("Sheet1").columns("A:AQ").AutoFit()

'シート名の設定
xlSheet.name = vasJigyosyoName 」
と言う処理をこのファンクションで行わないで、
別ファンクションを呼び出して、(xlSheetsとxlSheetを引数で渡します)
処理を行った所プロセスが無事開放されました。

なぜ、この解決方法で大丈夫だったのか、また
この解決方法は合っているのかと言うのが疑問に残るため、
引き続きご教授願いたいと思います。

じゃんぬねっと
ぬし
会議室デビュー日: 2004/12/22
投稿数: 7811
お住まい・勤務地: 愛知県名古屋市
投稿日時: 2006-12-19 12:42
引用:

エドさんの書き込み (2006-12-19 11:45) より:

xlSheets("Sheet1").columns("A:AQ").AutoFit()


これはダメでしょう。

他の COM ラッパーオブジェクトは変数に参照を取り、
ReleaseComObject メソッドで参照カウントをデクリメントしています。
しかし、この部分だけは、なぜかそれが漏れています。
(他の部分はどこかからコピー & ペーストしたものだと思いますが)

引用:

xlSheets("Sheet1").columns("A:AQ").AutoFit()


まず、xlSheets("Sheet1") の時点で Excel.Worksheet オブジェクトの参照を取り忘れています。
さらに、Columns と Column で合計 3 つの参照の取り忘れがあります。

参照の取り忘れがある時点で、参照カウントのデクリメント漏れが確定していることになります。
このあたりは、過去ログで散々話題になっていることなので、

引用:

色々なサイトで上記の件名を検索して調べたのですが、


再度見直した方が良いでしょう。
どのサイトにも書いてある基本中の基本のことです。

_________________
C# と VB.NET の入門サイト
じゃんぬねっと日誌
エド
常連さん
会議室デビュー日: 2006/12/07
投稿数: 43
投稿日時: 2006-12-21 10:44
返答遅くなりました。

自分なりに理解した部分に関して、つまり
xlSheets("Sheet1").columns("A:AQ").AutoFit()
を使うには、
columnsとcolumnを上の宣言部でDimで定義してから
Dim xlcolumns as object
Dim xlcolumn as object
各定義されたものを
MRComObject(xlcolumns)
MRComObject(xlcolumn)
と放り込んで開放してあげればよいと言う事でしょうか。

引用:
--------------------------------------------------------------------------------
じゃんぬねっとさんの書き込み (2006-12-19 12:42) より:

再度見直した方が良いでしょう。

--------------------------------------------------------------------------------

確かに、定義なしでいきなりオブジェクトを使うのはNGであると
書いてありました。↓参照
http://homepage1.nifty.com/rucio/main/technique/teq_15.htm

参照の取り忘れについて少し分からないのですが、

引用:
--------------------------------------------------------------------------------
じゃんぬねっとさんの書き込み (2006-12-19 12:42) より:

xlSheets("Sheet1") の時点で Excel.Worksheet オブジェクトの参照を取り忘れています。
--------------------------------------------------------------------------------

とはどういう事でしょうか?
Dim xlSheets As Object = xlBook.Worksheets
と宣言して、
xlSheets の処理を行い、
MRComObject(xlSheets)
だけでは足りないと言う事でしょうか?
SheetもBookと同じ様にCloseしてあげなきゃいけないのでしょうか??

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