DOBON.NETプログラミング道掲示板

■35490 / 親記事)  vb.netでExcelファイル操作
  
□投稿者/ Excel難しい 一般人(1回)-(2023/08/22(Tue) 14:55:20)
  • アイコン環境/言語:[vb.net .NET Franework 4.7.2] 
    分類:[.NET] 

    どうしてもタスクマネージャーのバックグラウンドプロセスにExcelが残ってしまいます。
    何か変な所ありますでしょうか?

    Dim ex As New Microsoft.Office.Interop.Excel.Application
    Dim wb As Microsoft.Office.Interop.Excel.Workbook = ex.Workbooks.Open(txtpath.Text)

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

    ex.Quit()
    System.Runtime.InteropServices.Marshal.ReleaseComObject(ex)
    ex = Nothing

    MsgBox(“作成完了”)
マルチポストを報告
違反を報告
引用返信 削除キー/
■35491 / ResNo.1)  Re[1]: vb.netでExcelファイル操作
□投稿者/ Hongliang 大御所(645回)-(2023/08/22(Tue) 15:04:03)
  • アイコン#35487 とだいぶそっくりなご質問のようです。
    そちらに回答がついていますが、参考になりませんでしょうか。
違反を報告
引用返信 削除キー/
■35492 / ResNo.2)  Re[2]: vb.netでExcelファイル操作
□投稿者/ Excel難しい 一般人(4回)-(2023/08/22(Tue) 15:13:00)
  • アイコンNo35491に返信(Hongliangさんの記事)
    > #35487 とだいぶそっくりなご質問のようです。
    > そちらに回答がついていますが、参考になりませんでしょうか。

    申し訳ありません。
    #35478を参考に色々試してみたのですが、改善されなかった
    ため、一度最低限のエクセルファイルを開いて閉じるプログラムのみに
    変更してみたのですがうまく行かず…
    オブジェクトの解放する方法が間違って居るのでしょうか?
違反を報告
引用返信 削除キー/
■35493 / ResNo.3)  Re[1]: vb.netでExcelファイル操作
□投稿者/ 魔界の仮面弁士 大御所(1559回)-(2023/08/22(Tue) 15:37:00)
  • アイコンNo35490に返信(Excel難しいさんの記事)
    No35487 の 独学学生さんとは別の方ですか?

    > Dim ex As New Microsoft.Office.Interop.Excel.Application
    これは良いとして

    > Dim wb As Microsoft.Office.Interop.Excel.Workbook = ex.Workbooks.Open(txtpath.Text)
    ここが違いますね。 No35488 で Hongliang さんも書かれているように:

    Dim books As Microsoft.Office.Interop.Excel.Workbooks = ex.Workbooks
    Dim wb As Microsoft.Office.Interop.Excel.Workbook = books.Open(txtpath.Text)

    のように、COM オブジェクトごとに別々の変数に保持してください。

    > wb.Close(False)
    > System.Runtime.InteropServices.Marshal.ReleaseComObject(wb)

    なので、Workbooks も解放せねばなりません。
    System.Runtime.InteropServices.Marshal.ReleaseComObject(books)

    > wb = Nothing
    > ex.Quit()
    > System.Runtime.InteropServices.Marshal.ReleaseComObject(ex)
    > ex = Nothing
違反を報告
引用返信 削除キー/
■35499 / ResNo.4)  Re[2]: vb.netでExcelファイル操作
□投稿者/ Excel難しい 一般人(6回)-(2023/08/23(Wed) 07:03:10)
  • アイコン独学学生と同じです!!

    では、シートを指定したい場所は

    Dim sheet As Microsoft.Office.Interop.Excel.Worksheets= ex.Worksheets

    Dim sh As Microsoft.Office.Interop.Excel.Worksheet =sheet(“発注”)

    になるって事でしょうか??
違反を報告
引用返信 削除キー/
■35500 / ResNo.5)  Re[3]: vb.netでExcelファイル操作
□投稿者/ 魔界の仮面弁士 大御所(1560回)-(2023/08/23(Wed) 15:12:12)
  • アイコン2023/08/24(Thu) 14:05:02 編集(投稿者)

    No35499に返信(Excel難しいさんの記事)
    > 独学学生と同じです!!
    ここの掲示板の利用ルールには
     「一貫した名前を使用し、投稿によって名前を変えないでください」
     「投稿者名を変えて投稿できないことになっています」
    と明記されています。無暗に変更しないようにしましょう。
    https://dobon.net/vb/bbs/index.html#manners


    COM オブジェクトの Excel を直接参照設定して使うのは、バージョン不一致時の互換性問題や、
    オブジェクトの解放手順の煩雑さの点や、現状ではあまりおすすめできません。
    利用者がストアアプリ版の Office を利用していると呼び出せませんし。

    要件次第では ClosedXml / NetOffice / SpreadSheetLight / などを用いることも検討してみてください。
    「実行環境に Excel 本体が無くても呼び出せる」というメリットもあります。



    > では、シートを指定したい場所は
    毎回 Microsoft.Office.Interop.Excel を書くのは煩わしいので、
    通常は、ファイル先頭に Imports ステートメントを書いて省略します。
    (もしくは、プロジェクト単位のユーザーインポートを使う手法もある)
    実際には案2を使っている事例が多いですね。


    <案1>
    Imports Microsoft.Office.Interop
    Public Class Form1
     Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
      Dim ex As New Excel.Application() With { .Visible = True }


    <案2>
    Imports Excel = Microsoft.Office.Interop.Excel
    Public Class Form1
     Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
      Dim ex As New Excel.Application() With { .Visible = True }


    > Dim sheet As Microsoft.Office.Interop.Excel.Worksheets= ex.Worksheets
    Sheets や Worksheets といったコレクション オブジェクトは、「複数形」の型名なので
    sheet といった「単数形」の変数名にすると、後で読むときにややこしくないですか…?

    それはさておき、外部からアクセスする場合は、Application の Worksheets プロパティを使用しないでください。
    Worksheets にアクセスする前に、Workbooks に対して Add か Open を呼び出して
    Workbook オブジェクトを取得し、そのあとで Worksheets プロパティにアクセスします。

    > Dim sh As Microsoft.Office.Interop.Excel.Worksheet =sheet(“発注”)
    Worksheets プロパティはその戻り値として
    実は Worksheets 型ではなく Sheets 型のオブジェクトを返します。
    型が異なるため、Worksheets 型には代入できません。

    そのため .NET から扱う場合は
     Dim sheet As Excel.Sheets = book.Worksheets
    のように書くことになります。

    とはいえ、VB2008 以降なら型推論が使えるので、単に
     Dim sheets = book.Worksheets
    と書くのが簡単でしょう。

    Worksheet に関しては、そこから
     Dim sheet1 = DirectCast(sheets("Sheet1"), Excel.Worksheet)
    とします。単に
     Dim sheet1 = sheets("Sheet1")
    にしてしまうと、As Object になってしまうため、DirectCast で本来の型に戻します。


    そのほか、間違えやすいのが Cells プロパティ。

    VBA においては
     sheet1.Cells(1, 1).Value = 123
    などと書けますが、Cells プロパティは実は「引数を持たないプロパティ」であり、
    内部的には、既定のプロパティを通じて
     sheet1.sheet1.Cells.[_Default](1,1).Value = 123
    に相当する VBA コードになります。

    さらに、Range オブジェクトもまた、COM のオブジェクトであるため、
    上記の VBA コードを .NET の世界で扱うときには、
     Dim cells As Excel.Range = sheet1.Cells
     Dim cell1 As Excel.Range = cells(1, 1)
     cell1.Value = 100
     Marshal.ReleaseComObject(cell1)
     Marshal.ReleaseComObject(cells)
    という手続きを踏まねばなりません。


    もう一つ厄介なのかが「暗黙の型変換」。
    Excel の幾つかのメソッドやプロパティには、COM オブジェクトを引数に持つメンバーがありますが、
    この時に、明示的な型ではなく、汎用型の As Object な引数で受けわたしてしまうと、
    その時点で COM の参照カウントが増大し、Marshal.ReleaseComObject を呼んでも
    即時に解放されにくくなってしまうことがあります。
    (Object 型ではなく、本来の型の変数を引数に渡すのであればセーフ)

    参照カウントが増大していた場合、Marshal.ReleaseComObject の呼び出し後、
    戻り値が 0 にならないため、そこで判断することができます。
    (要件次第では、FinalReleaseComObject に切り替えることも検討)
違反を報告
引用返信 削除キー/
■35502 / ResNo.6)  Re[4]: vb.netでExcelファイル操作
□投稿者/ Excel難しい 一般人(7回)-(2023/08/24(Thu) 11:13:46)
  • アイコン度々申し訳ありません。
    ワークブックとワークシートはおそらく解放できていると思うのですがまだプロセスが残ってしまいます。

    Dim lastRow As Integer = sh.Cells(sh.Rows.Count, "B").End(Microsoft.Office.Interop.Excel.XlDirection.xlUp).row
    とrange.ClearContents()をコメントにし、
    range = Cells.Range("B5:I" & lastRow)をrange = Cells.Range("B5:I5")に変更するとプロセスが残らず終了できるので、lastRowかrange.ClearContents()のどちらかが解放漏れしていてプロセスが残ってしまっていると考えて居るのですが、解放の仕方が分からない状態です。
    お力を貸してください。

    Private Sub btn作成_Click(sender As Object, e As EventArgs) Handles btn作成.Click
    If txtPath.Text = "" Then
    Exit Sub
    End If

    Dim str As String = ""
    Dim strCount = 0

    Dim ex As New Microsoft.Office.Interop.Excel.Application
    Dim books As Microsoft.Office.Interop.Excel.Workbooks = ex.Workbooks
    Dim wb As Microsoft.Office.Interop.Excel.Workbook
    Dim sheets As Microsoft.Office.Interop.Excel.Sheets
    Dim sh As Microsoft.Office.Interop.Excel.Worksheet

    Dim Cells As Microsoft.Office.Interop.Excel.Range
    Dim range As Microsoft.Office.Interop.Excel.Range

    Try
    For rw As Integer = 0 To dgv.RowCount - 1
    If strCount = 0 Then
    wb = books.Open(txtPath.Text)
    sheets = wb.Worksheets
    sh = sheets("発注一覧")
    Cells = sh.Cells
    strCount = strCount + 1

            'ファイル内のセル初期化
    Dim lastRow As Integer = sh.Cells(sh.Rows.Count, "B").End(Microsoft.Office.Interop.Excel.XlDirection.xlUp).row
    If lastRow >= 5 Then
    range = Cells.Range("B5:I" & lastRow)
    range.ClearContents()
    End If

    End If

    strCount = strCount + 1
    End If
    Next

    wb.Save()
    wb.Close(False)
    ex.Quit()


    Catch exc As Exception
    Finally
    If Cells IsNot Nothing Then
    System.Runtime.InteropServices.Marshal.ReleaseComObject(Cells)
    End If
    If range IsNot Nothing Then
    System.Runtime.InteropServices.Marshal.ReleaseComObject(range)
    End If
    If sheets IsNot Nothing Then
    System.Runtime.InteropServices.Marshal.ReleaseComObject(sheets)
    End If
    If sh IsNot Nothing Then
    System.Runtime.InteropServices.Marshal.ReleaseComObject(sh)
    End If
    If books IsNot Nothing Then
    System.Runtime.InteropServices.Marshal.ReleaseComObject(books)
    End If
    If wb IsNot Nothing Then
    System.Runtime.InteropServices.Marshal.ReleaseComObject(wb)
    End If
    If ex IsNot Nothing Then
    System.Runtime.InteropServices.Marshal.ReleaseComObject(ex)
    End If

    Cells = Nothing
    range = Nothing
    sheets = Nothing
    sh = Nothing
    books = Nothing
    wb = Nothing
    ex = Nothing

    GC.Collect()
    GC.WaitForPendingFinalizers()

    btn作成.Enabled = True

    If strCount <> 0 Then
    MsgBox(“作成完了”)
    End If
    End Try

    End Sub
違反を報告
引用返信 削除キー/
■35503 / ResNo.7)  Re[5]: vb.netでExcelファイル操作
□投稿者/ 魔界の仮面弁士 大御所(1561回)-(2023/08/24(Thu) 14:16:09)
  • アイコン2023/08/24(Thu) 17:37:12 編集(投稿者)

    No35502に返信(Excel難しいさんの記事)
    > Dim lastRow As Integer = sh.Cells(sh.Rows.Count, "B").End(Microsoft.Office.Interop.Excel.XlDirection.xlUp).row

    先の回答がまるで反映されていないですよね…?
    Range オブジェクトすべてを変数にとり、使用後に廃棄しましょう。

    'Dim lastRow As Integer = sh.Cells(sh.Rows.Count, "B").End(Microsoft.Office.Interop.Excel.XlDirection.xlUp).row
    Dim cells = sh.Cells
    Dim rows = sh.Rows
    Dim rng = cells(rows.Count, "B")
    Dim rngEnd = rng.End(Microsoft.Office.Interop.Excel.XlDirection.xlUp)

    Dim lastRow As Integer = rngEnd.Row

    Marshal.RelaseComObject(rngEnd)
    Marshal.RelaseComObject(rng)
    Marshal.RelaseComObject(rows)
    Marshal.RelaseComObject(cells)

    正直なところ、生の Excel ライブラリを直接扱うのは、COM リソースの管理が面倒なので、
    先に紹介したような、Managed リソースで管理できるライブラリを使った方が楽だと思いますよ。


    > range = Cells.Range("B5:I" & lastRow)を
    この処理自体には何の問題もありません。
    実際のところ、
     Dim lastRow As Integer = 5
     range = Cells.Range("B5:I" & lastRow)
    であれば、問題は出ないですよね?

    問題があったのは、lastRow を求めるための先の手続きです。


    > Private Sub btn作成_Click(sender As Object, e As EventArgs) Handles btn作成.Click
    strCount が何をカウントしているのかの意図が不明瞭ですが、
    提示されたコードでは「If 〜 Then」よりも「End If」の方が多く、文法的に意味が通りません。
    正しいコードを提示しましょう。


    それと、COM オブジェクトの扱いに根本的な誤解があるようです。
     Dim o1 = sheet1.Cells
     Dim o2 = sheet1.Cells
    この場合、o1 と o2 は同一のオブジェクトを指しているように見えますが、
    実際は別物であり、 If o1 Is o2 Then は False となります。

    なのでこのケースでは、
     Marshal.ReleaseComObject(o2)
     Marshal.ReleaseComObject(o1)
    のように、それぞれを解放せねばなりません。

    それゆえに
     Dim o As Excel.Range = Nothing
     For n = 1 To 2
      o = sheet1.Cells
      :
     Next
     Marshal.ReleaseComObject(o)
    のような再代入処理が行われると、Range オブジェクトの解放漏れに繋がります。
    ※Range 以外の COM オブジェクト(Workbooks とか Workbook とか Sheets とか Worksheet とか)も同様です。

    ただし、
     For n = 1 To 2
      Dim o As Excel.Range = sheet1.Cells
      :
      Marshal.ReleaseComObject(o)
     Next
    のように、取得 → 解放の手続きをその都度行うのであれば、問題ありません。
    (とはいえ、何度も取得しなおす方法では、実行コストが高くなってしまいますが)


    > For rw As Integer = 0 To dgv.RowCount - 1
    >  If strCount = 0 Then
    >   wb = books.Open(txtPath.Text)
    >   sheets = wb.Worksheets
    >   sh = sheets("発注一覧")
    ということで、この書き方は NG 。

    そもそも、COM オブジェクトの問題が無かったとしても、
    このコードもあまりに不自然というものです。

    ループ内で、txtPath.Text が変化するわけでは無いのですから、
    毎回、COM オブジェクトを再取得する意味は無いはずです。
    ループの外で処理すれば済む話でしょう。

    Dim wb = books.Open(txtPath.Text)
    Dim sheets = wb.Worksheets
    Dim sh = sheets("発注一覧")
    For rw = 0 To dgv.RowCount - 1
      :
    Next
    Marshal.ReleaseComObject(sh)
    Marshal.ReleaseComObject(sheets)
    wb.Save()
    wb.Close(False)
    Marshal.ReleaseComObject(wb)
    ex.Quit()
    Marshal.ReleaseComObject(ex)


    それにしても、『For rw As Integer = 0 To dgv.RowCount - 1』って何の意味があるのでしょうか?
    dgv が DataGridView であるというのは想像がつきますが、
    ループ内で変数 rw が一度も使われていませんし、ループさせる意味が皆無に見えるのですが。
違反を報告
引用返信 削除キー/



スレッド内ページ移動 / << 0 >>

このスレッドに書きこむ

Mode/  Pass/


- Child Tree -