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

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

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

Re[2]: VBからExcelを立ち上げた際の、解放処理について


(過去ログ 63 を表示中)

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

■36324 / inTopicNo.1)  VBからExcelを立ち上げた際の、解放処理について
  
□投稿者/ hito (8回)-(2009/05/27(Wed) 14:20:13)

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

2009/05/27(Wed) 14:20:45 編集(投稿者)
お世話になります。
現在、VB2008にて、Windowsフォームの内容をExcelに書き込むという処理をしています。
既存のExcelファイルを開き、DataGridViewとTextBoxの値を書き込んだ後、別名を付けて保存するという
仕様にするためにソースを書いたのですが、タスクマネージャーで確認すると、プロセスが残っており、
正しく終了することができません。
大変申し訳ないのですが、以下のソースの中から、解放処理について間違っている部分をご指摘していただけないでしょうか。
以下が、そのソースになります。
Private Sub Sample()
   Dim xlApp As Excel.Application
   Dim xlBook As Excel.Workbook
   Dim xlSheet As Excel.Worksheet
   '---------------------
   ' EXCELファイルを開く
   '---------------------
   xlApp = CreateObject("Excel.Application") 'Application生成
   xlBook = xlApp.Workbooks.Open("C:\TEMP\sample.xls")
   xlApp.Visible = True          
   xlSheet = DirectCast(xlBook.ActiveSheet, Excel.Worksheet) 'Worksheet

   '--------------
   ' シートの編集
   '--------------
  'TextBoxの値を特定のCellに書き込む。
   Dim xlCells As Excel.Range = xlSheet.Cells
   Dim xlCell As Excel.Range = xlCells(3, 1)

   xlCell.Value = TextBox1.Text

   Dim xlce As Excel.Range = xlCells(3, 2)
   xlce.Value = TextBox2.Text

   Dim xlcel As Excel.Range = xlCells(3, 12)
   xlcel.Value = TextBox3.Text

	'DataGridViewの内容をExcelに書き込む。
        Dim X As Integer = 0
        Dim Y As Integer = 0
        'ループでDataGridViewの指定した範囲の値を書き込む
        For i As Integer = 0 To DataGridView1.Rows.Count - 1
            For j As Integer = 0 To DataGridView1.Columns.Count - 1
                If j < 16 Then
                    '書き込みは6行目から行う。
                    If j <> 6 Then
                        DirectCast(xlSheet.Cells(i + 6, j + 1), Excel.Range).Value = DataGridView1(j, i).Value
                    End If
                End If
                
		'特定の位置のCellの値が2の場合は、その行の色を変更する。
                If DataGridView1(5, i).Value = 2 Then
                    xlSheet.Range(xlSheet.Cells(i + 6, 1), xlSheet.Cells(i + 6, 16)).Interior.ColorIndex = 45
                ElseIf DataGridView1(5, i).Value = 3 Then
                    xlSheet.Range(xlSheet.Cells(i + 6, 1), xlSheet.Cells(i + 6, 16)).Interior.ColorIndex = 37
                End If
            Next j
        Next i

   '保存時の問合せを非表示に設定。
   xlApp.DisplayAlerts = False
   'Worksheet を名前をつけて保存します。
   xlSheet.SaveAs("C:\TEMP\test.xls")

   MRComObject(xlCells)
   MRComObject(xlCells)

   MRComObject(xlCells)
   MRComObject(xlCells)

   MRComObject(xlCells)
   MRComObject(xlCells)

   MRComObject(xlSheet) 'xlSheet の解放
   MRComObject(xlBook)  'xlBook の解放
   xlApp.Quit() 'Excelを閉じる 
   MRComObject(xlApp)   'xlApp を解放

   '保存するExcelファイルの解放・終了処理
        
   '解放処理を行う
   MRComObject(xlSheet) 'xlSheet の解放
   MRComObject(xlBook)  'xlBook の解放
   'xlApp.Quit() 'Excelを閉じる 
   MRComObject(xlApp)   'xlApp を解放

   '*********************
   '      解放処理
   '*********************
   xlCells
   xlSheet = Nothing
   xlBook = Nothing
   xlApp = Nothing

End Sub
長文になってしまい、申し訳ありません。
サイトなどを見て、違う書き方やなどを試しても見たのですが、どうしても原因や間違った部分が分かりませんでした。
大変恐縮ですが、よろしくお願いいたします。

引用返信 編集キー/
■36331 / inTopicNo.2)  Re[1]: VBからExcelを立ち上げた際の、解放処理について
□投稿者/ 魔界の仮面弁士 (1090回)-(2009/05/27(Wed) 15:17:05)
No36324 (hito さん) に返信
> xlApp = CreateObject("Excel.Application") 'Application生成
参照設定しているのだから、
 xlApp = New Excel.Application()
で良いのでは無いかと。
CreateObject だと、Object 型になってしまいますし。

> xlBook = xlApp.Workbooks.Open("C:\TEMP\sample.xls")
ここが NG 。「.」が 2 連続で使われていたら要注意と思ってください。

一度、Workbooks プロパティを変数に受けて、
 hoge = xlApp.Workbooks
 xlBook = hoge.Open("C:\TEMP\sample.xls")
のようにしてください。

COM オブジェクトを扱う場合、基本的には、
 オブジェクト.プロパティ.プロパティ
 オブジェクト.プロパティ.メソッド
 オブジェクト.メソッド.メソッド
 オブジェクト.メソッド.プロパティ
などを行うと、COM オブジェクトの解放漏れの原因となります。
(ただし、「名前空間.列挙型名.メンバ」の 2連ドットの場合は問題ありません)


> DirectCast(xlSheet.Cells(i + 6, j + 1), Excel.Range).Value = DataGridView1(j, i).Value
これも NG。「xlSheet.Cells」の解放漏れと、DirectCast 結果の 2 つが漏れています。

まず、xlCells には「xlSheet.Cells」が代入済みなのですから、それを使えば 1 つ目の漏れは防げます。
そして 2 つ目の方には、新たに変数を用意して、
 Dim a As Excel.Range = DirectCast(xlCells(i + 6, j + 1), Excel.Range)
 a.Value = DataGridView1(j, i).Value
 Marshal.ReleaseComObjcet(a)
のように記述する事になるでしょう。


> MRComObject(xlCells)
MRComObject の実装が書かれていないので(推測はつきますが)、判断に困りますが,
何故、ここに 6 連続で xlCells を渡しているのでしょうか?
引用返信 編集キー/
■36336 / inTopicNo.3)  Re[2]: VBからExcelを立ち上げた際の、解放処理について
□投稿者/ hito (9回)-(2009/05/27(Wed) 15:42:45)
No36331 (魔界の仮面弁士 さん) に返信
回答ありがとうございます。
>>   xlApp = CreateObject("Excel.Application") 'Application生成
> 参照設定しているのだから、
>  xlApp = New Excel.Application()
> で良いのでは無いかと。
> CreateObject だと、Object 型になってしまいますし。

最初にExcelを開くソースをサイトで見つけたときに、そのままコピーしてしまいました。

指摘していただいた部分を以下のように修正をしました。

xlApp = New Excel.Application()'Application生成
Dim xlBooks As Excel.Workbooks = xlApp.Workbooks
Dim xlBook = xlBooks.Open("C:\TEMP\sample.xls")
xlApp.Visible = True          'EXCELの表示
xlSheet = DirectCast(xlBook.ActiveSheet, Excel.Worksheet) 'Worksheet

xlBookを変数として宣言して行うこととしました。
ですが、やはりプロセスが残ってしまいます。
>Dim a As Excel.Range = DirectCast(xlCells(i + 6, j + 1), Excel.Range)
>a.Value = DataGridView1(j, i).Value
>Marshal.ReleaseComObjcet(a)
の部分ですが、こちらのソースの、「Marshal.ReleaseComObjcet(a)」
というのは、書き込みを行っているループ内で書くのでしょうか、

今までは、書き込み → 解放 という形にしていたのですが・・・

>>   MRComObject(xlCells)
> MRComObject の実装が書かれていないので(推測はつきますが)、判断に困りますが,
> 何故、ここに 6 連続で xlCells を渡しているのでしょうか?

すみません、そこは間違って連続で貼り付けてしまっていました。

> MRComObject の実装が書かれていないので(推測はつきますが)、
こちらが、ソースになります。
  ''' <summary>
    ''' COMオブジェクト開放処理
    ''' </summary>
    ''' <remarks>
    ''' 使用したCOMオブジェクトの開放を行う
    ''' </remarks>
    Private 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
ただ、このソースも自分で書いたわけではないので、具体的な説明ができません。
すみません、一応

引用返信 編集キー/
■36338 / inTopicNo.4)  Re[3]: VBからExcelを立ち上げた際の、解放処理について
□投稿者/ 魔界の仮面弁士 (1091回)-(2009/05/27(Wed) 16:08:44)
2009/05/27(Wed) 16:11:39 編集(投稿者)

No36336 (hito さん) に返信
> xlSheet = DirectCast(xlBook.ActiveSheet, Excel.Worksheet) 'Worksheet
特に理由がない限りは、Active 系、Selection 系のメンバはできる限り使用しない方が無難かと。

たとえばフォームの操作で、
 Dim txt As TextBox = Me.ActiveControl
 txt.Text = "TEST"
などと書く事は稀で、通常は、
 TextBox1.Text = "TEST"
などと、どのコントロールを操作対象にするかを明示しますよね。

それと同様、
 xlSheets = xlBook.Worksheets
 xlSheet = DirectCast(xlSheets(シート名またはシート番号), Excel.Worksheet)
のように、操作対象を明らかにしておいた方が安全です。


> の部分ですが、こちらのソースの、「Marshal.ReleaseComObjcet(a)」
> というのは、書き込みを行っているループ内で書くのでしょうか、
その通りです。MRComObject でも良いですけれどね。

というのも、
 Dim x As COMオブジェクト型
 x = 新しいCOMオブジェクトへの参照1
 x = 新しいCOMオブジェクトへの参照2
という処理を行ってしまうと、1 の解放が行われないからです。
そのため、2 を代入する前に、1 を(MRComObject 等で)始末する必要があります。


>>MRComObject の実装が書かれていないので(推測はつきますが)、
> こちらが、ソースになります。
それ、元ソースは私のコードです。。。

元々は、
 http://support.microsoft.com/kb/317109/ja
の NAR の実装を元に、花ちゃんさんが
 http://hanatyan.sakura.ne.jp/index.html
のサイトで、MRComObject というメソッドを作成されていたのですが、
それに私が手を加えた物が、hito さんが現在使っている MRComObject です。

# VBレスキュー VB.NET 掲示板の 2007-09-28 22:18 の私の投稿がそれ。


> ただ、このソースも自分で書いたわけではないので、具体的な説明ができません。
必要であれば解説しますが、ブラックボックスで良いなら放置の方向で。
引用返信 編集キー/
■36340 / inTopicNo.5)  Re[4]: VBからExcelを立ち上げた際の、解放処理について
□投稿者/ hito (10回)-(2009/05/27(Wed) 16:26:22)
No36338 (魔界の仮面弁士 さん) に返信
> >>MRComObject の実装が書かれていないので(推測はつきますが)、
>>こちらが、ソースになります。
> それ、元ソースは私のコードです。。。

> 元々は、
>  http://support.microsoft.com/kb/317109/ja
> の NAR の実装を元に、花ちゃんさんが
>  http://hanatyan.sakura.ne.jp/index.html
> のサイトで、MRComObject というメソッドを作成されていたのですが、
> それに私が手を加えた物が、hito さんが現在使っている MRComObject です。
> 
> # 具体的には、花ちゃんさんのサイトでいうところの、VB.NET 掲示板の No6374 (2007-09-28 22:18)です。

そうだったんですか、いろいろなサイトを巡ってようやく発見したと最初は思っていたのですが、
巡り巡って本人の下に帰ってきたんですね。

ちなみにですが、すみません、1つ載せていなかったソースがありました。
    Try
            xlBook.Close(False) 'xlBook を閉じる
        Catch ex As Exception
        End Try
です。
このあとに、
        'xlBooksの解放
        MRComObject(xlSheet) 'xlSheet の解放
        MRComObject(xlBooks)  'xlBook の解放
        MRComObject(xlBook)  'xlBook の解放
        xlApp.Quit() 'Excelを閉じる 
        MRComObject(xlApp)   'xlApp を解放
と記述しているのですが、

xlApp,xlBook,xlBooksなどの変数によって、解放させる順番があるのでしょうか、
つまり、「xlBookを開放する前に、xlAppを解放しなければならない」などの制限があるのであれば、
そこが原因ではないかと考えているのですが・・・。

引用返信 編集キー/
■36347 / inTopicNo.6)  Re[5]: VBからExcelを立ち上げた際の、解放処理について
□投稿者/ 魔界の仮面弁士 (1094回)-(2009/05/27(Wed) 18:56:16)
2009/05/28(Thu) 01:05:28 編集(投稿者)

No36340 (hito さん) に返信
> つまり、「xlBookを開放する前に、xlAppを解放しなければならない」などの制限があるのであれば、

開放(open)と解放(release)が混在しているのが気になりますが、それはさておき。

一応、オブジェクトの階層順に処理した方が良いかも知れませんね。

Worksheet.SaveAs を使うのであれば、

 MRComObject(Rangeオブジェクト)
 Worksheet.SaveAs
 MRComObject(Worksheetオブジェクト)
 MRComObject(Sheetsコレクション)
 Workbook.Close
 MRComObject(Workbookオブジェクト)
 MRComObject(Workbooksコレクション)
 Application.Quit
 MRComObject(Applicationオブジェクト)

で、Workbook.SaveAs を使うのであれば、

 MRComObject(Rangeオブジェクト)
 MRComObject(Worksheetオブジェクト)
 MRComObject(Sheetsコレクション)
 Workbook.SaveAs
 Workbook.Close
 MRComObject(Workbookオブジェクト)
 MRComObject(Workbooksコレクション)
 Application.Quit
 MRComObject(Applicationオブジェクト)

かな。


> そこが原因ではないかと考えているのですが・・・。

まずは、どの行を実行したときに解放漏れが発生しているのか、問題箇所を突き止めましょう。

コードを単純化して、データの書き込みは行わず、Application インスタンスのみを
 起動→即時終了
とするだけにして、正しく解放されることを確認してみてください。

 xlApp = New Excel.Application()
 xlApp.Visible = True
 MsgBox("起動確認")
 xlApp.Quit()
 MRComObject(xlApp)


それで問題が無いようであれば、徐々にコードの実行の範囲を広げていきましょう。

 起動→ファイルを開く→終了
 起動→ファイルを開く→ファイルを閉じる→終了
 起動→ファイルを開く→ファイルを保存→ファイルを閉じる→終了
 起動→ファイルを開く→データの書き込み→ファイルを保存→ファイルを閉じる→終了
引用返信 編集キー/
■36369 / inTopicNo.7)  Re[1]: VBからExcelを立ち上げた際の、解放処理について
□投稿者/ 魔界の仮面弁士 (1098回)-(2009/05/28(Thu) 01:18:47)
ぁゃιぃ箇所を見落としていたので追記。

No36324 (hito さん) に返信
> Dim xlCells As Excel.Range = xlSheet.Cells
> Dim xlCell As Excel.Range = xlCells(3, 1)
> Dim xlce As Excel.Range = xlCells(3, 2)
> Dim xlcel As Excel.Range = xlCells(3, 12)
それぞれ、忘れずに Marshal.ReleaseComObject してくださいね。


> xlSheet.Range(xlSheet.Cells(i + 6, 1), xlSheet.Cells(i + 6, 16)).Interior.ColorIndex = 45
ここで大量に解放漏れがあるようです。この場合は、
 Dim xlCells As Excel.Range = xlSheet.Cells
 Dim r1 As Excel.Range = xlCells(i + 6, 1)
 Dim r2 As Excel.Range = xlCells(i + 6, 16)
 Dim rngTarget As Excel.Range = xlSheet.Range(r1, r2)
 Dim it As Excel.Interior = rngTarget.Interior
 it.ColorIndex = 45
のように、それぞれの COM オブジェクトを変数に受けておき、使用後に解放する必要があるかと。
引用返信 編集キー/
■36384 / inTopicNo.8)  Re[2]: VBからExcelを立ち上げた際の、解放処理について
□投稿者/ hito (11回)-(2009/05/28(Thu) 10:58:09)
2009/05/28(Thu) 12:47:15 編集(投稿者)

No36369 (魔界の仮面弁士 さん) に返信

ありがとうございます。
無事、プロセスを完全に終了し、Excelファイルを開くことができるようになりました。

> xlSheet.Range(xlSheet.Cells(i + 6, 1), xlSheet.Cells(i + 6, 16)).Interior.ColorIndex = 45
この部分は、指定した範囲のCellの背景色を変えるという部分なんですが、
この部分は、「行」の単位でプロセスの終了を考えていたのですが、他の部分と同じように、
Cell単位で行う必要があったのですね。

今回のことで、多くのことが学べました。
ありがとうございました。
解決済み
引用返信 編集キー/


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

このトピックに書きこむ

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

管理者用

- Child Tree -