知らなければ良かったのですが、「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解放を理解したほうが近道だと判断しました。どちらにするかは皆さんで判断して良いと思います。気になる方は他のライブラリも試してください。
プロセスが残っても気にしなければ済むかもしれませんが、出来上がったプログラムを操作する人のスキルを考慮すると、何かのたびに呼ばれる可能性があります。できるだけ問題も芽は潰しておきたほうが無難です。