0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

vb.netでCOM参照オブジェクトを使用してExcelを扱う

Last updated at Posted at 2016-12-15

COM参照オブジェクトはあんまり良くない。

・COM参照オブジェクトはプロセスの解放漏れが非常に発生しやすい
※下記サンプルに詳しく書いてあります。
・低速(実装方法によって改善は可能)
・Excelのインストールが必須

なので、まだオープンソースライブラリ等を検討できるならそちらの導入をお勧めします。

サンプル1 読み込み

''' <summary>
''' 入力元Excel読み込み処理
''' </summary>
''' <param name="filePath">Excelファイルパス</param>
''' <remarks>
''' ※Excelの読み込みでCOM参照を使用しているため、解放処理を厳密に記載すること。
''' ※解放処理を確実に行うため、以下のルールを厳守すること。
''' ・宣言したすべてのCOMオブジェクトに対し、Finally句で解放処理を行う。←解放漏れを確実に防ぐため。
''' 
''' ・COM解放は、子階層のものから解放すること。←子階層が解放されていないと親階層が解放されない。
'''   Application
'''   └Workbooks
'''     └Workbook
'''       └Worksheets
'''         └Worksheet
'''           └Range    ←この階層から解放する
''' 
''' ・子階層のCOMオブジェクトを使用する場合、1つ上の親オブジェクトを宣言しそこからアクセスする。
'''   NG例:Dim excelApp As New Excel.Application
'''         Dim wkbk As Excel.Workbook = Nothing
'''         wkbk = Application.Workbooks.Open(FilePath) ←Workbooksが暗黙変換され、解放できないExcelプロセスができる。
''' 
'''   OK例:Dim excelApp As New Excel.Application
'''         Dim wkbks As Excel.Workbooks = Nothing
'''         Dim wkbk As Excel.Workbook = Nothing
'''         wkbks = excelApp.Workbooks	
'''         wkbk = wkbks.Open(ustrFilePath)
''' 
''' ・DirectCastはCOMオブジェクトには使わない。←キャストされたCOMオブジェクトは解放できない。
''' </remarks>
Private Function ReadExcelData(ByVal filePath As String) As List(Of clsDataExcelInput)

    Dim retList As New List(Of clsDataExcelInput)

    Dim excelApplication As New Excel.Application()
    Try '1.excelApplication解放処理用Try

        Dim workbooks As Excel.Workbooks = excelApplication.Workbooks
        Try '2.workbooks解放処理用Try

            Dim workbook As Excel.Workbook = workbooks.Open(filePath)
            Try '3.workbook解放処理用Try

                Dim worksheets As Excel.Sheets = workbook.Sheets()
                Try '4.worksheets解放処理用Try

                    '■全シートを参照
                    For sheet As Integer = 1 To worksheets.Count

                        Dim sheetData As New clsDataExcelInput
                        Dim worksheet As Excel.Worksheet = CType(worksheets(sheet), Excel.Worksheet)
                        Try '5.worksheet解放処理用Try

                            'シート名Eは終了を示すので、以降のシートは読み込まない
                            If worksheet.Name.Trim = "E" Then
                                Exit For
                            End If

                            '■ヘッダ部分を参照
                            For Each cell As KeyValuePair(Of String, String) In pHashHeaderCellAddress

                                Dim range As Excel.Range = Nothing
                                Try '6.Range解放処理用Try
                                    If cell.Value = "" Then
                                        Continue For
                                    End If

                                    'セルデータをデータクラスにセット
                                    range = worksheet.Range(cell.Value)
                                    CallByName(sheetData, cell.Key, CallType.Set, range.Value)

                                Finally
                                    '6.Range解放処理
                                    If range IsNot Nothing Then
                                        Marshal.ReleaseComObject(range)
                                    End If
                                End Try

                            Next

                            '■明細列を参照
                            For row As Integer = LineFrom To LineSu + LineFrom - 1
                                For Each col As KeyValuePair(Of String, String) In pHashMeisaiColAddress

                                    Dim range As Excel.Range = Nothing
                                    Try '6.Range解放処理用Try
                                        If col.Value = "" Then
                                            Continue For
                                        End If
                                        range = worksheet.Range(col.Value & row.ToString)
                                        '取り消し線設定時は処理しない
                                        If range.Font.Strikethrough IsNot Nothing AndAlso CBool(range.Font.Strikethrough) = True Then
                                            Exit For
                                        End If
                                        CallByName(sheetData, col.Key, CallType.Set, range.Text)

                                    Finally
                                        '6.Range解放処理
                                        If range IsNot Nothing Then
                                            Marshal.ReleaseComObject(range)
                                        End If
                                    End Try

                                Next
                            Next

                            'シート名取得
                            sheetData.SheetName = worksheet.Name

                        Finally
                            '5.worksheet解放処理
                            Marshal.ReleaseComObject(worksheet)
                        End Try

                        '戻り値データを設定
                        retList.Add(sheetData)
                    Next

                Finally
                    '4.worksheets解放処理
                    Marshal.ReleaseComObject(worksheets)
                End Try

            Finally
                '3.workbook解放処理
                If workbook IsNot Nothing Then
                    excelApplication.DisplayAlerts = False
                    workbook.Close()
                End If
                Marshal.ReleaseComObject(workbook)
            End Try

        Finally
            '2.workbooks解放処理用Try
            Marshal.ReleaseComObject(workbooks)
        End Try

    Finally
        '1.excelApplication解放処理
        If excelApplication IsNot Nothing Then
            excelApplication.Quit()
        End If
        Marshal.ReleaseComObject(excelApplication)
    End Try

    Return retList

End Function

サンプル2 書き出し

''' <summary>
''' Excel出力処理
''' </summary>
''' <param name="outputFolder">出力フォルダパス</param>
''' <param name="xml">出力先情報XMLファイル</param>
''' <param name="outputData">Excel出力データ</param>
''' <remarks>
''' ※Excelの読み込みでCOM参照を使用しているため、解放処理を厳密に記載すること。
''' ※解放処理を確実に行うため、以下のルールを厳守すること。
''' ・宣言したすべてのCOMオブジェクトに対し、Finally句で解放処理を行う。←解放漏れを確実に防ぐため。
''' 
''' ・COM解放は、子階層のものから解放すること。←子階層が解放されていないと親階層が解放されない。
'''   Application
'''   └Workbooks
'''     └Workbook
'''       └Worksheets
'''         └Worksheet
'''           └Range    ←この階層から解放する
''' 
''' ・子階層のCOMオブジェクトを使用する場合、1つ上の親オブジェクトを宣言しそこからアクセスする。
'''   NG例:Dim excelApp As New Excel.Application
'''         Dim wkbk As Excel.Workbook = Nothing
'''         wkbk = Application.Workbooks.Open(FilePath) ←Workbooksが暗黙変換され、解放できないExcelプロセスができる。
''' 
'''   OK例:Dim excelApp As New Excel.Application
'''         Dim wkbks As Excel.Workbooks = Nothing
'''         Dim wkbk As Excel.Workbook = Nothing
'''         wkbks = excelApp.Workbooks	
'''         wkbk = wkbks.Open(ustrFilePath)
''' 
''' ・DirectCastはCOMオブジェクトには使わない。←キャストされたCOMオブジェクトは解放できない。
''' </remarks>
Private Sub WriteExcelData(ByVal outputFolder As String, ByVal xml As clsDataXmlTemplate, ByVal outputData As List(Of DataSet))


    Dim excelApplication As New Excel.Application()
    Dim frmProgress As XlsExportProgress = Nothing
    Try '1.excelApplication解放処理用Try

        frmProgress = New XlsExportProgress("部品表単価を出力中です。", outputData.Count)
        frmProgress.Show()

        'メッセージ表示を無しに変更
        excelApplication.DisplayAlerts = False

        Dim workbooks As Excel.Workbooks = excelApplication.Workbooks
        Try '2.workbooks解放処理用Try

            '出力先ファイル
            Dim outputFilePath As String = Nothing
            Try
                '■テンプレートから出力ファイルを作成
                With True

                    'ファイルパス取得
                    Dim templatePath As String = cmnFileAccess.JoinPath(csAppSetting.DllFilesPath, "Excel\" & xml.baseFileName).ToString

                    Dim seiban As String = outputData(0).Tables(clsDataExcelInput.cTableNameHeader).Rows(0)(clsDataExcelInput.enumColHeader.Seiban).ToString
                    outputFilePath = cmnFileAccess.JoinPath(outputFolder, "加工部品表_" & seiban & "_" & Now.ToString("yyyyMMddhhmm") & ".xls").ToString

                    System.IO.File.Copy(templatePath, outputFilePath, True)

                End With

                Dim workbook As Excel.Workbook = workbooks.Open(outputFilePath)
                Try '3.workbook解放処理用Try

                    Dim worksheets As Excel.Sheets = workbook.Sheets()
                    Try '4.worksheets解放処理用Try

                        '■全データ出力
                        For i_data As Integer = 0 To outputData.Count - 1

                            Dim worksheet As Excel.Worksheet = Nothing
                            Dim sheetData As New clsDataExcelInput
                            Try '5.worksheet解放処理用Try

                                '■テンプレートシートから出力シートを生成
                                With True
                                    Dim templateSheet As Excel.Worksheet = CType(worksheets(1), Excel.Worksheet)
                                    Dim lastSheet As Excel.Worksheet = CType(worksheets(worksheets.Count), Excel.Worksheet)
                                    Try
                                        templateSheet.Copy(After:=lastSheet)
                                        worksheet = CType(worksheets(worksheets.Count), Excel.Worksheet)
                                    Finally
                                        'テンプレートで使ったシート解放
                                        Marshal.ReleaseComObject(templateSheet)
                                        Marshal.ReleaseComObject(lastSheet)
                                    End Try
                                End With

                                '■ヘッダ部分を参照
                                With outputData(i_data).Tables(clsDataExcelInput.cTableNameHeader)
                                    worksheet.Name = .Rows(0)("SheetName").ToString
                                    For Each col As DataColumn In .Columns
                                        Dim range As Excel.Range = Nothing
                                        Try '6.Range解放処理用Try

                                            Dim cell As DataRow() = xml.dsHeaderStyle.Tables("columnsSettings").Select("key = '" & col.ColumnName & "'")
                                            If cell(0)("value").ToString = "" Then
                                                Continue For
                                            End If

                                            'セルデータをデータクラスにセット
                                            range = worksheet.Range(cell(0)("value").ToString)
                                            range.Value = .Rows(0)(col).ToString
                                        Finally
                                            '6.Range解放処理
                                            If range IsNot Nothing Then
                                                Marshal.ReleaseComObject(range)
                                            End If
                                        End Try
                                    Next
                                End With

                                '■明細列を参照
                                With outputData(i_data).Tables(clsDataExcelInput.cTableNameMeisai)
                                    For i_row As Integer = 0 To .Rows.Count - 1

                                        Dim rowWrite As String = (CInt(xml.dsDetailStyle.Tables("detailHeaderSettings").Select("key = 'detailHeaderUnitRow'")(0)("value")) + i_row + 1).ToString
                                        For Each col As DataColumn In .Columns
                                            Dim range As Excel.Range = Nothing
                                            Try '6.Range解放処理用Try
                                                If .Rows(i_row)(col) Is DBNull.Value OrElse .Rows(i_row)(col) Is Nothing Then
                                                    Continue For
                                                End If
                                                '書き込みセルを取得
                                                Dim colWrite As String = xml.dsDetailStyle.Tables("columnsSettings").Select("key = '" & col.ColumnName & "'")(0)("value").ToString.Split(","c)(0)

                                                'セルデータをデータクラスにセット
                                                range = worksheet.Range(colWrite & rowWrite)
                                                range.Value = .Rows(i_row)(col).ToString
                                            Finally
                                                '6.Range解放処理
                                                If range IsNot Nothing Then
                                                    Marshal.ReleaseComObject(range)
                                                End If
                                            End Try
                                        Next

                                    Next
                                End With

                            Finally
                                '5.worksheet解放処理
                                Marshal.ReleaseComObject(worksheet)
                            End Try

                            'プログレスバーカウントUP
                            frmProgress.AddCountProgress()
                        Next

                        'テンプレートシートを削除
                        With True
                            Dim templateSheet As Excel.Worksheet = CType(worksheets(1), Excel.Worksheet)
                            Try
                                templateSheet.Select()
                                templateSheet.Delete()
                            Finally
                                'テンプレートで使ったシート解放
                                Marshal.ReleaseComObject(templateSheet)
                            End Try
                        End With

                    Finally
                        '4.worksheets解放処理
                        Marshal.ReleaseComObject(worksheets)
                    End Try

                    '■書き込み内容を保存
                    workbook.SaveAs(outputFilePath, Excel.XlFileFormat.xlExcel8)

                Finally
                    '3.workbook解放処理
                    If workbook IsNot Nothing Then
                        workbook.Close()
                    End If
                    Marshal.ReleaseComObject(workbook)
                End Try

            Catch ex As Exception
                '処理失敗時は作成した出力ファイルを削除
                If outputFilePath IsNot Nothing AndAlso System.IO.File.Exists(outputFilePath) Then
                    System.IO.File.Delete(outputFilePath)
                End If
                Throw ex
            End Try
        Finally
            '2.workbooks解放処理用Try
            Marshal.ReleaseComObject(workbooks)
        End Try

    Finally

        If frmProgress IsNot Nothing Then
            frmProgress.Close()
        End If

        '1.excelApplication解放処理
        If excelApplication IsNot Nothing Then
            excelApplication.Quit()
        End If
        Marshal.ReleaseComObject(excelApplication)
    End Try

End Sub
0
2
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?