「Visual Basic」EXCELプロセスを残さないコーディング

*本サイトはアフィリエイト広告を利用しています。

知らなければ良かったのですが、「Microsoft.Office.Interop.Excel」でのEXCEL操作は、COMオブジェクトの解放を行わないとプロセスが残ります。

EPPlusなど、オープンソースライブラリであればプロセスは残らないとのことですが、他のオープンソースを学ぶのは時間がかかりそうな気がしました。

Ipteropを使う方が近道と考えて、COMオブジェクト解放を試してみました。

環境は「Visual Studi0 2022」です。

スポンサーリンク
スポンサーリンク

プロセスが残るNGコーディングのサンプル

COM解放行わないサンプルコーディングです。
このようなコーディングはEXCELプロセスが残ります。

Dim xl As Excel.Application = New Excel.Application
Dim wbs As Excel.Workbooks = xl.Workbooks
Dim wb As Excel.Workbook = wbs.Open("D:\data.xls")

MsgBox(wb.Sheets("Sheet1").cells(1, 1).value)

wb.Close()
xl.Quit()
スポンサーリンク

EXCELプロセスを残さないコーディングのサンプル

Dim xl As Excel.Application = New Excel.Application
Dim wbs As Excel.Workbooks = xl.Workbooks
Dim wb As Excel.Workbook = wbs.Open("D:\data.xls")
Dim sh As Excel.Sheets = wb.Sheets
Dim ws As Excel.Worksheet = sh("sheet1")
Dim cs As Excel.Range = ws.Cells
Dim rg As Excel.Range

rg = cs(1, 1)

MsgBox(rg.Value)

wb.Close()
xl.Quit()

Marshal.ReleaseComObject(rg)
Marshal.ReleaseComObject(cs)
Marshal.ReleaseComObject(ws)
Marshal.ReleaseComObject(sh)
Marshal.ReleaseComObject(wb)
Marshal.ReleaseComObject(wbs)
Marshal.ReleaseComObject(xl)

以下に気をつけてコーディングしました。

オブジェクトを全て変数宣言する

「EXCEL → Workbooks → Workbook → Sheets → Worksheet → range」の順番で、全てのオブジェクトを変数宣言します。

Excelのオブジェクト階層に沿って変数宣言します。

ピリオドを2個以上使わない

オブジェクト変数を使用して、ピリオドを2個以上使わないようにコーディングします。

「wb.Sheets(“Sheet1”).cells(1, 1).value」のように、ピリオドを何個もつなげたコーディングはEXCELプロセスが残ります。

COMオブジェクトはすべて解放する

終了時には、全てのオブジェクト変数を「Marshal.ReleaseComObject(変数)」で開放します。すべてのオブジェクトを開放しないとダメなようで、解放もれがあるとEXCELプロセスは残りました。

僕の環境ではNothingは記入しなくても大丈夫でした。

Excelを開いたままプログラムを終了する

Excelを閉じないで続けて作業するケースを想定して、Excelを開いたままの処理を試しました。

Dim xl As Excel.Application = New Excel.Application
Dim wbs As Excel.Workbooks = xl.Workbooks
Dim wb As Excel.Workbook = wbs.Open("D:\data.xls")
Dim sh As Excel.Sheets = wb.Sheets
Dim ws As Excel.Worksheet = sh("sheet1")
Dim cs As Excel.Range = ws.Cells
Dim rg As Excel.Range

rg = cs(1, 1)

MsgBox(rg.Value)

xl.Visible = True   'Excelの可視化

'wb.Close()         ※コメントアウト
'xl.Quit()          ※コメントアウト

'Marshal.ReleaseComObject(rg)  ※コメントアウト
'Marshal.ReleaseComObject(cs)  ※コメントアウト
'Marshal.ReleaseComObject(ws)  ※コメントアウト
'Marshal.ReleaseComObject(sh)  ※コメントアウト
'Marshal.ReleaseComObject(wb)  ※コメントアウト

Marshal.ReleaseComObject(wbs)
Marshal.ReleaseComObject(xl)
  • WorkbooksとExcelのCOMは解放する。
  • 他は解放しなくても大丈夫。

WorkbooksとExcel以外のCOMオブジェクトは、解放しなくてもプロセスは残らないという、以外な結果でした。

繰り返し処理でCOM解放する

繰り返し処理でのCOM解放を試しました。

以下はOKコーディングです。

結果としては、ループ内で使用したオブジェクトはループ内で開放しないと、EXCELプロセスが残りました。

Dim xl As Excel.Application = New Excel.Application
Dim wbs As Excel.Workbooks = xl.Workbooks
Dim wb As Excel.Workbook = wbs.Open("D:\data.xls")
Dim sh As Excel.Sheets = wb.Sheets
Dim ws As Excel.Worksheet = sh("sheet1")
Dim cs As Excel.Range = ws.Cells

Dim i As Integer
Dim rg As Excel.Range

For i = 1 To 10
    rg = cs(i, 1)
    Debug.Print(rg.Value)
    Marshal.ReleaseComObject(rg)    'ループ内でリリースする
Next i

wb.Close()
xl.Quit()

Marshal.ReleaseComObject(cs)
Marshal.ReleaseComObject(ws)
Marshal.ReleaseComObject(sh)
Marshal.ReleaseComObject(wb)
Marshal.ReleaseComObject(wbs)
Marshal.ReleaseComObject(xl)

オブジェクトの変数宣言はループ内でも大丈夫でした。

For i = 1 To 10
    Dim rg As Excel.Range           'ループ内で宣言する
    rg = cs(i, 1)
    Debug.Print(rg.Value)
    Marshal.ReleaseComObject(rg)    'ループ内でリリースする
Next i

以下のような、ループ外でのリリースはEXCELプロセスが残りました。

Dim rg As Excel.Range

For i = 1 To 10
    rg = cs(i, 1)
    Debug.Print(rg.Value)
Next i

Marshal.ReleaseComObject(rg)    'ループ外でリリースはEXCLEプロセスが残る

Rowsを使用して行全体を処理する

EXCELの行を処理するコーディングのサンプルです。

Dim xl As Excel.Application = New Excel.Application
Dim wbs As Excel.Workbooks = xl.Workbooks
Dim wb As Excel.Workbook = wbs.Open("D:\data.xls")
Dim sh As Excel.Sheets = wb.Sheets
Dim ws As Excel.Worksheet = sh("sheet1")
Dim rws As Excel.Range = ws.Rows
Dim rw As Excel.Range

rw = rws(1)
rw.Select()

wb.Close()
xl.Quit()

Marshal.ReleaseComObject(rw)
Marshal.ReleaseComObject(rws)
Marshal.ReleaseComObject(ws)
Marshal.ReleaseComObject(sh)
Marshal.ReleaseComObject(wb)
Marshal.ReleaseComObject(wbs)
Marshal.ReleaseComObject(xl)

行単位での処理を行うにはRangeオブジェクトにRowsを指定します。

行単位でコピーする処理で使うので試しました。

列を処理するにはColumnsでできると思います。

データが存在する最終行の取得

EXCELからのデータ取得で頻度が高い「データが存在する最終行の取得」を試しました。

Dim xl As Excel.Application = New Excel.Application
Dim wbs As Excel.Workbooks = xl.Workbooks
Dim wb As Excel.Workbook = wbs.Open("D:\data.xls")
Dim sh As Excel.Sheets = wb.Sheets
Dim ws As Excel.Worksheet = sh("sheet1")
Dim cs As Excel.Range = ws.Cells
Dim rws As Excel.Range = cs.Rows

Dim shRowCell As Excel.Range
Dim shDataEnd As Excel.Range

shRowCell = cs(rws.Count, 1)                        'シートの最終行
shDataEnd = shRowCell.End(Excel.XlDirection.xlUp)   'シートの最終行から上に移動

MsgBox(shDataEnd.Row)                               '最終行を表示する

wb.Close()
xl.Quit()

Marshal.ReleaseComObject(shDataEnd)
Marshal.ReleaseComObject(shRowCell)

Marshal.ReleaseComObject(cs)
Marshal.ReleaseComObject(rws)
Marshal.ReleaseComObject(ws)
Marshal.ReleaseComObject(sh)
Marshal.ReleaseComObject(wb)
Marshal.ReleaseComObject(wbs)
Marshal.ReleaseComObject(xl)


「cs(rws.Count, 1) .End(Excel.XlDirection.xlUp).Row」
このようなコーディングはEXCELプロセスが残ります。プロセスを残さないためには、1個づつ丁寧に処理してください。

少し複雑ですね、もう少し良いコーディングがあるかも知れないですね。

まとめ

ネットを見ると様々な情報があります。なかには「解放しなくても良い」、「まとめて解放できる」などの情報もあります。もちろんそれで出来ているなら問題ありませんが、僕にはできませんでした。Interopでプロセスを残さずにプログラムを終了するには、1個づつ丁寧にCOM解放を行うのが良いと思いました。

EPPlusなどの他の方法も良いかもしれませんが、どちらが良いかはその人次第ですね。僕はInteropを選びました。Interopのほうが情報が多くて、Visual BasicでExcelの多くの操作をするには、EPPlusをおぼえるよりも、COM解放を理解したほうが近道だと判断しました。どちらにするかは皆さんで判断して良いと思います。気になる方は他のライブラリも試してください。

プロセスが残っても気にしなければ済むかもしれませんが、出来上がったプログラムを操作する人のスキルを考慮すると、何かのたびに呼ばれる可能性があります。できるだけ問題も芽は潰しておきたほうが無難です。

タイトルとURLをコピーしました