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

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

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

Re[2]: [VB.net] EXCELプロセスが解放できない


(過去ログ 139 を表示中)

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

■81762 / inTopicNo.1)  [VB.net] EXCELプロセスが解放できない
  
□投稿者/ Artor (4回)-(2016/11/10(Thu) 16:52:16)

分類:[.NET 全般] 

いつも大変お世話になっております。
下記のようなEXCELの読み込みプログラムを作成しています。
すべてのオブジェクトを開放しているつもりなのですが、EXCELプロセスが1つ残ってしまいます。
2度3度と繰り返し実行しても、残るプロセスは1つだけです。
どこに不備があるのか、どなたか、アドバイスを頂けませんでしょうか??

・環境
VB.net、.net framework 4

・EXCEL
シート1:シート上の5つのセルにそれぞれ名前が付けられている
シート2:A列(A1〜A5)にシート1の名前5つが記載されている

・プログラム仕様
シート2のA列に記載されている名前から、シート1の該当セルの値を読み取り、
データテーブルに入れて返す

Protected Function test(ByVal strFilePath As String, ByVal strFileName As String) As DataTable

Dim dt As New DataTable 'データテーブル(戻り値)
Dim dr As DataRow = dt.NewRow 'データテーブル行
Dim objExcel As Excel.Application = Nothing 'EXCEL
Dim objBook As Excel.Workbook = Nothing 'BOOK
Dim objSheets As Excel.Sheets = Nothing 'SHEETS
Dim objSheet1 As Excel.Worksheet = Nothing 'シート1
Dim objRange1 As Excel.Range = Nothing 'シート1の範囲
Dim objSheet2 As Excel.Worksheet = Nothing 'シート2
Dim objRange2 As Excel.Range = Nothing 'シート2の範囲
Dim strName As String = String.Empty 'シート2のA列に記載された名前
Dim row As Long = 1 'シート2の行


'EXCELオープン
objExcel = New Excel.Application
objExcel.DisplayAlerts = False
objExcel.Visible = False
objBook = objExcel.Workbooks.Add(strFilePath & strFileName)
objSheets = objBook.Worksheets
objSheet1 = objSheets.Item(1)
objSheet2 = objSheets.Item(2)
objRange2 = objSheet2.Cells

'シート2のA列に値が存在する間ループ
Do While Not String.IsNullOrEmpty(objRange2(row, 1).text)

'シート2名前定義からシート1の読取箇所を特定
strName = objRange2(row, 1).text
objRange1 = objSheet1.Range(strName)

'データテーブルにカラム追加
dt.Columns.Add(strName, Type.GetType("System.String"))

'データテーブル行にシート1から読取った値をセット
dr(strName) = objRange1.Value

readRow = readRow + 1

Loop

'データテーブルに行追加
dt.Rows.Add(dr)


'COMオブジェクト解放
If Not objRange1 Is Nothing Then
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(objRange1)
objRange1 = Nothing
End If

If Not objRange2 Is Nothing Then
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(objRange2)
objRange2 = Nothing
End If

If Not objSheet1 Is Nothing Then
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(objSheet1)
objSheet1 = Nothing
End If

If Not objSheet2 Is Nothing Then
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(objSheet2)
objSheet2 = Nothing
End If

If Not objSheets Is Nothing Then
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(objSheets)
objSheets = Nothing
End If

If Not objBook Is Nothing Then
objBook.Close(False)
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(objBook)
objBook = Nothing
End If

If Not objExcel Is Nothing Then
objExcel.ScreenUpdating = True
objExcel.DisplayAlerts = True
objExcel.Quit()
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(objExcel)
objExcel = Nothing
End If

'ガーベージコレクタ動作
System.GC.Collect()

Return dt

End Function

引用返信 編集キー/
■81763 / inTopicNo.2)  Re[1]: [VB.net] EXCELプロセスが解放できない
□投稿者/ furu (77回)-(2016/11/10(Thu) 17:35:03)
No81762 (Artor さん) に返信

objBook = objExcel.Workbooks.Add(strFilePath & strFileName)

Workbooksの解放?
引用返信 編集キー/
■81764 / inTopicNo.3)  Re[2]: [VB.net] EXCELプロセスが解放できない
□投稿者/ Artor (5回)-(2016/11/10(Thu) 19:01:49)
furu様

ご返信ありがとうございます。
下記のようなイメージで記載すれば良いということでしょうか??

Dim objWorkbooks As Excel.Workbooks = Nothing

objWorkbooks = objExcel.Workbooks

objBook = objWorkbooks.Add(strFilePath & strFileName)

If Not objWorkBooks Is Nothing Then
objBook.Close(False)
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(objWorkBooks)
objWorkBooks = Nothing
End if

引用返信 編集キー/
■81765 / inTopicNo.4)  Re[3]: [VB.net] EXCELプロセスが解放できない
□投稿者/ Hongliang (469回)-(2016/11/10(Thu) 19:09:20)
> Do While Not String.IsNullOrEmpty(objRange2(row, 1).text)
のobjRange2(row, 1)が返してきたオブジェクトもかな?
引用返信 編集キー/
■81766 / inTopicNo.5)  Re[4]: [VB.net] EXCELプロセスが解放できない
□投稿者/ Artor (6回)-(2016/11/10(Thu) 19:43:04)
Hongliang様

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

>>Do While Not String.IsNullOrEmpty(objRange2(row, 1).text)
> のobjRange2(row, 1)が返してきたオブジェクトもかな?

申し訳ございません。
ご指摘のオブジェクトを解放する方法が分かりません…
可能であれば、もう少し詳しくお教え頂けますでしょうか?
何か変数を定義してオブジェクトセットするのかな…とは思うのですが、コード内でどう記述すれば良いかが分からないです。
引用返信 編集キー/
■81767 / inTopicNo.6)  Re[5]: [VB.net] EXCELプロセスが解放できない
□投稿者/ furu (78回)-(2016/11/10(Thu) 20:48:23)
No81766 (Artor さん) に返信

VB.net 知らないので、プログラムは書けないです。

ExcelなどのCOM オブジェクトは
丁寧に丁寧に解放してやらないとプロセスが残ってしまいます。

Artorさんのプログラムで、Workbooks.AddのWorkbooksや
objRange2(row, 1).textのobjRange2(row, 1)のように
ピリオドの前のやつは常に解放が必要です。

Do While Not String.IsNullOrEmpty(objRange2(row, 1).text)
は、こんな感じです。

  Do While 真 '無限ループ
    aaa = objRange2(row, 1)
        sss = aaa.text
        System.Runtime.InteropServices.Marshal.FinalReleaseComObject(aaa)
        if String.IsNullOrEmpty(sss) ループを抜ける

気持ちが伝わりますでしょうか?

引用返信 編集キー/
■81768 / inTopicNo.7)  Re[6]: [VB.net] EXCELプロセスが解放できない
□投稿者/ Artor (8回)-(2016/11/10(Thu) 21:38:40)
furu様

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

> Artorさんのプログラムで、Workbooks.AddのWorkbooksや
> objRange2(row, 1).textのobjRange2(row, 1)のように
> ピリオドの前のやつは常に解放が必要です。

この点はよく理解できました。ありがとうございます。

WorkbooksやobjRange2(row,1) を格納するオブジェクトをそれぞれ作成し、
使用後にそれらを解放してやればよいということですね。
実装方法については勉強不足で、中々ピンと来ないのですが、お教え頂いた内容を参考に、ループの条件を見直してみたいと思います。
引用返信 編集キー/
■81776 / inTopicNo.8)  Re[7]: [VB.net] EXCELプロセスが解放できない
□投稿者/ なちゃ (148回)-(2016/11/12(Sat) 20:06:05)
全部きちんと解放するのが本来は正しいのですが、はっきり言って不毛な努力なので、処理全体を独立したメソッドに切り出した上で、全部処理が終わったら呼び出し側でGC起こしてしまうのも手ではあります。
特にクライアントアプリでは。
GUIアプリならBeginInvokeでGC起動するメソッドををポストしても良いです。

引用返信 編集キー/
■81786 / inTopicNo.9)  Re[1]: [VB.net] EXCELプロセスが解放できない
□投稿者/ 魔界の仮面弁士 (940回)-(2016/11/14(Mon) 16:18:13)
No81762 (Artor さん) に返信
> シート1:シート上の5つのセルにそれぞれ名前が付けられている
> シート2:A列(A1〜A5)にシート1の名前5つが記載されている
> シート2のA列に記載されている名前から、シート1の該当セルの値を読み取り、
> データテーブルに入れて返す

1 行 5 列の DataTable ということでしょうか。
この条件だと、Cells プロパティの出番は無い気がします。


Private Function Sample(ByVal folderPath As String, ByVal fileName As String) As DataTable
    Dim ds As New DataSet()
    Dim dt As DataTable = ds.Tables.Add(fileName)
    dt.CaseSensitive = True

    Dim xlApp As New Excel.Application()
    xlApp.DisplayAlerts = False
    xlApp.Visible = Debugger.IsAttached
    Dim xlBooks As Excel.Workbooks = xlApp.Workbooks
    Dim fullPath As String = Path.Combine(folderPath, fileName)
    Dim xlBook As Excel.Workbook = xlBooks.Add(fullPath)
    Dim xlSheets As Excel.Sheets = xlBook.Worksheets
    Dim xlSheet1 = DirectCast(xlSheets(1), Excel.Worksheet)
    Dim xlSheet2 = DirectCast(xlSheets(2), Excel.Worksheet)

    'シート2:A列(A1〜A5)にシート1の名前5つが記載されている
    Dim xlRange2 As Excel.Range = xlSheet2.Range("A1:A5")
    Dim oValue As Object = xlRange2.Value
    Marshal.ReleaseComObject(xlRange2)

    Dim sNames = DirectCast(oValue, Object(,)).OfType(Of String)()
    sNames = sNames.Where(Function(sName) Not String.IsNullOrEmpty(sName)).Distinct()

    'シート1:シート上の5つのセルにそれぞれ名前が付けられている
    Dim dr As DataRow = dt.NewRow()
    For Each sName In sNames
        dt.Columns.Add(sName)
        Dim xlRange1 As Excel.Range = xlSheet1.Range(sName)
        dr(sName) = xlRange1.Text
        Marshal.ReleaseComObject(xlRange1)
    Next

    Marshal.ReleaseComObject(xlSheet2)
    Marshal.ReleaseComObject(xlSheet1)
    Marshal.ReleaseComObject(xlSheets)
    xlBook.Close(False)
    Marshal.ReleaseComObject(xlBook)
    Marshal.ReleaseComObject(xlBooks)
    xlApp.ScreenUpdating = True
    xlApp.DisplayAlerts = True
    xlApp.Quit()
    Marshal.ReleaseComObject(xlApp)

    dt.Rows.Add(dr)
    dt.AcceptChanges()

    Return dt
End Function

引用返信 編集キー/
■81787 / inTopicNo.10)  Re[1]: [VB.net] EXCELプロセスが解放できない
□投稿者/ 魔界の仮面弁士 (941回)-(2016/11/14(Mon) 17:36:58)
No81762 (Artor さん) に返信
> Dim row As Long = 1

As Integer(Int32) に書き換えることをお奨めします。
As Long(Int64) でも間違いというわけでは無いのですが、
行数は最大でも &H100000 行(1,048,576)なので、Int32 で十分なはず。

※ Int32.MaxValue = 2,147,483,647


64bit 版 Excel なら、Int64 値を直接扱えるのですが、
32bit 版 Excel では、Variant の内部処理形式としてしか
Int64 値を扱えないため、不都合を生じる事が稀にあるので。


> objBook = objExcel.Workbooks.Add(strFilePath & strFileName)

これが NG なのは既出ですね。
Workbooks コレクションも解放対象です。


> objSheet1 = objSheets.Item(1)
> objSheet2 = objSheets.Item(2)

Sheets コレクションのインデクサは Object 型を返しますので、
DirectCast を併用した方が丁寧です。


> objRange2 = objSheet2.Cells
> Do While Not String.IsNullOrEmpty(objRange2(row, 1).text)

この場合、
 objRange3 = DirectCast(objRange2(row, 1), Excel.Range)
 s = objRange3.Text
として、objRange3 の解放が必要になります。

ただし A1:A5 のように連続している領域を読み取るのであれば、
No81786 のように複数セルをまとめて配列として受け取った方が効率が良いでしょう。


> 'シート2名前定義からシート1の読取箇所を特定
> strName = objRange2(row, 1).text

ここも同様ですね。
「objRange2(row, 1)」が返す Range オブジェクトも解放対象です。



>  'データテーブルにカラム追加
>  dt.Columns.Add(strName, Type.GetType("System.String"))

同じ列名が二重登録されると DuplicateNameException の例外になりますので、
No81786 では、Sheet2 に同じ名前が記録されていた場合についても対処しています。

もし、存在しない名前が記録されていた場合にも対処するのであれば、
Names コレクションを併用すると良いでしょう。


>  readRow = readRow + 1

先ほどの「Dim row As Long = 1」とは別物でしょうか?
readRow は未定義ですし、row は未使用なようですが…。


> 'データテーブルに行追加
> dt.Rows.Add(dr)

追加した直後の DataRow は、行の状態が Added になるため、
No81786 では、AcceptChanges メソッドを呼んで Unchanged にしています。


>  System.Runtime.InteropServices.Marshal.FinalReleaseComObject(objRange1)

これは Final 付きでも Final 無しでも構いません。
今回のようなケースであれば、ReleaseComObject でも十分ですね。

たとえば Range プロパティの引数に Range オブジェクトを渡すような場合、
内部的に参照カウントが増加することがあるため、FinalReleaseComObject を
呼ばないと解放されないことがあります。


>  'ガーベージコレクタ動作
>  System.GC.Collect()

文書によって、ガーベージ、ガーベジ、ガベージなどと表記ゆれがありますが、
Microsoft としては、VB や C# に対してはガベージ表記で統一しているようです。

なお、オブジェクト参照が残っている状態で GC.Collect を呼んだ場合、
むしろ解放処理が遅れる要因にもなりえます。GC に任せるのも選択肢の一つですが、
No81786 では意図的に、GC.Collect を呼ばないコードとして記述しています。
引用返信 編集キー/
■81790 / inTopicNo.11)  Re[2]: [VB.net] EXCELプロセスが解放できない
□投稿者/ Artor (9回)-(2016/11/14(Mon) 19:45:16)
2016/11/14(Mon) 19:47:19 編集(投稿者)
2016/11/14(Mon) 19:47:14 編集(投稿者)

解決致しましたので、解決済みチェックさせていただきました。ありがとうございました。
解決済み
引用返信 編集キー/
■81791 / inTopicNo.12)  Re[2]: [VB.net] EXCELプロセスが解放できない
□投稿者/ Artor (10回)-(2016/11/14(Mon) 19:45:51)
なちゃ様、 魔界の仮面弁士様

大変丁寧なご返答を頂き、ありがとうございます!!
返答が遅くなりまして、申し訳ございません。
皆様から頂いたアドバイスを参考にさせていただき、無事にプロセスを解放することができました。
この度は、色々と勉強させて頂きました。
ご協力いただき、本当にありがとうございました。
引用返信 編集キー/


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

このトピックに書きこむ

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

管理者用

- Child Tree -