Excelがプロセスに居座ってしまう!?

他サイトでは、Excelに関するオブジェクト全てを参照の都度解放せよと書いてあります。
「見解」というものもあるかも知れませんが.... 他サイトでこの件に触れている記述を見てみると、Excelに関するCOMオブジェクト全てを参照の都度解放せよという見解を示しているところが目立ちます。 これはExcelに関する「Application」「Workbooks」「Workbook」「Worksheets」「Worksheet」「Range」「PageSetup」「Interior」など 全てを含むのだという記述です。しかもループ処理などで同一のオブジェクト変数にそのまま次のオブジェクトの参照をセットする場合は、次の参照に移る前に以前の参照を解放すると書かれています。
もちろん、ここまで徹底すれば「完璧」なのでしょうが、逆にそこまでやらないとExcelのプロセスは解放されないのかが疑問になりました。 それに、ブック内のワークシートをFor Eachで巡回するような場合は解放の記述ができないだろうという書き方の都合も見えてきます。
ここでは簡単な実験を行なって違いを検証してみます。
結論は最後に書きましたが、結果はそれほど面倒な記述にする必要がないことが解りました。

まず、前提条件です。
私のところで.NETからExcelを取り扱うのは、印刷系の出力を目的としたものがほとんどです。 しかも、最近では直接「紙」に出力するのではなく、「Excelに出力してくれ」というのがほとんどの要望です。 ペーパーレス化が進んできているせいもありますが、やはり一覧形式はExcelで表現するものが見やすく、エンドユーザーが簡単に二次加工できるメリットが大きいのだと思います。
以前のページでも紹介しているように、原本となるExcelテンプレートを共有フォルダやWebサーバから開いて、そこにデータを貼り付けて表示させるという運用になっています。 従って、Excelを閉じるのはユーザーの手操作となります。 問題となるのは、作成したフォームが開いている状態でユーザーがExcelを閉じた後でプロセスにEXCEL.EXEが残ってしまうことで、何度も繰り返すとEXCEL.EXEが複数個プロセスに残存する現象となります。
前のページまでのサンプルはすでにこれらの対応を行なったものとなっていますが、重要な事項なので改めて説明しています。
動作確認した環境は、WindowsXP(32ビット)、7(32ビット・64ビット)、8.1(64ビット)、10(64ビット)で、Office側はExcel2003、2010、2013(全て32ビット)で、動作は全て同じ結果でした。(比較の仕方はそれほど厳格ではありません) 特にExcelを表示直後に閉じている場合は、EXCEL.EXEが残らないケースも多く見られます。Excelのバージョンが新しいほどこの傾向にあるような気がします。
なお、このページのサンプルソースの表示は「参照設定版」ですが、下記の画像からダウンロードできるソースは「参照設定版」「実行時バインド版」の両方を納めてあります。

簡単な記述ですから、動かしてみて下さい。
フォームにボタンを貼り付けただけです。
テスト用フォーム
(画像をクリックすると、プロジェクトフォルダの圧縮ファイルがダウンロードできます。)
ClickのイベントでExcelを起動し、ワークブックを追加してシート上に適当に文字を入力するなどして、ブックは閉じずExcelもそのままにして終了します。 ここで確認することは、本フォームをそのままにしておいて、手操作でブックを閉じてExcelを終了させた場合に、即座にEXCEL.EXEのプロセスも消失するのかを確かめることです。
タスクマネージャのプロセス
プロセスの状態は「タスクマネージャ」の「プロセス」タブで確認します。

以下、テストケースごとにソースコードを提示して説明します。

[Button1] Excel参照テスト①
まずは、他のサイトで勧めている「徹底的に解放する方法」です。
Excelの「Application」「Workbooks」「Workbook」「Worksheet」「Range」はそれぞれObject型の変数に参照を確保して、 同一変数を別の参照に利用する前にそれぞれ解放処置を行ないます。

    '***********************************************************************************************
    '   ■■■ コントロールイベント ■■■
    '***********************************************************************************************
    '* 処理名 :Button1_Click
    '* 機能  :ExcelのCOM参照テスト① ※新規ブックの追加①
    '-----------------------------------------------------------------------------------------------
    '* 返り値 :(なし)
    '* 引数  :(既定)
    '-----------------------------------------------------------------------------------------------
    '* 作成日 :2009年03月01日
    '* 作成者 :井上 治
    '* 更新日 :2017年04月23日
    '* 更新者 :井上 治
    '* 機能説明:※COMオブジェクトを参照した変数はその都度解放する
    '* 注意事項: ⇒[○]フォームを開いたままでもExcelを閉じるとプロセスも解放される
    '***********************************************************************************************
    Private Sub Button1_Click(ByVal sender As System.Object, _
                              ByVal e As System.EventArgs) Handles Button1.Click
        '-------------------------------------------------------------------------------------------
        Const cnsTitle = "ExcelのCOM参照テスト(新規ブックの追加①)"
        Dim objExcelApp As Excel.Application = Nothing          ' Excel.Application
        Dim objWorkbooks As Excel.Workbooks = Nothing           ' Excel.Workbooks
        Dim objWbk As Excel.Workbook = Nothing                  ' Excel.Workbook
        Dim objSh As Excel.Worksheet = Nothing                  ' Excel.Worksheet
        Dim objR As Excel.Range = Nothing                       ' Excel.Range
        '===========================================================================================
        ' Excel.Applicationのインスタンス生成(共通処理)
        If Not FP_GetExcelInstance(objExcelApp, cnsTitle) Then Exit Sub
        '-------------------------------------------------------------------------------------------
        ' ワークブックを追加
        objWorkbooks = objExcelApp.Workbooks
        objWbk = objWorkbooks.Add()
        With objWbk
            objSh = .Worksheets(1)
            With objSh
                objR = .Range("A1")
                objR.Value = "ABC"
                Call GP_ReleaseComObject(objR)                  ' ←参照の都度解放する
                objR = .Range("A2")
                objR.Value = "DEF"
                Call GP_ReleaseComObject(objR)                  ' ←参照の都度解放する
                objR = .Range("A3")
                objR.Value = "GHI"
                Call GP_ReleaseComObject(objR)                  ' ←参照の都度解放する
            End With
            Call GP_ReleaseComObject(objSh)                     ' ←参照の都度解放する
            ' ワークシートを追加
            objSh = .Worksheets.Add()
            With objSh
                .Name = "HOGE"
                objR = .Range("A1")
                objR.Value = "JKL"
                Call GP_ReleaseComObject(objR)                  ' ←参照の都度解放する
                objR = .Range("A2")
                objR.Value = "MNO"
                Call GP_ReleaseComObject(objR)                  ' ←参照の都度解放する
                objR = .Range("A3")
                objR.Value = "PQR"
                Call GP_ReleaseComObject(objR)                  ' ←参照の都度解放する
            End With
            Call GP_ReleaseComObject(objSh)                     ' ←参照の都度解放する
            .Saved = True
        End With
        '-------------------------------------------------------------------------------------------
        ' COM解放(こまめに)
        Call GP_ReleaseComObject(objWbk)
        Call GP_ReleaseComObject(objWorkbooks)
        Call GP_ReleaseComObject(objExcelApp)
    End Sub
当然ながら、フォームは開いたままでも、Excelを終了させた場合に即座にEXCEL.EXEのプロセスも消失します。

[Button2] Excel参照テスト②
では、「Worksheet」「Range」のオブジェクトを参照する変数は、利用都度解放しなければならないのかを試してみます。
このコードでは、「Worksheet」「Range」のオブジェクトを参照する変数は、参照都度解放はせずに最後にまとめて解放するようにしています。

    '***********************************************************************************************
    '* 処理名 :Button2_Click
    '* 機能  :ExcelのCOM参照テスト② ※新規ブックの追加②
    '-----------------------------------------------------------------------------------------------
    '* 返り値 :(なし)
    '* 引数  :(既定)
    '-----------------------------------------------------------------------------------------------
    '* 作成日 :2009年03月01日
    '* 作成者 :井上 治
    '* 更新日 :2017年04月23日
    '* 更新者 :井上 治
    '* 機能説明:※最後にまとめてCOMオブジェクトを参照した変数を解放する
    '* 注意事項: ⇒[○]フォームを開いたままでもExcelを閉じるとプロセスも解放される
    '              つまりループ中に都度解放するというような必要はないようだ
    '***********************************************************************************************
    Private Sub Button2_Click(ByVal sender As System.Object, _
                              ByVal e As System.EventArgs) Handles Button2.Click
        '-------------------------------------------------------------------------------------------
        Const cnsTitle = "ExcelのCOM参照テスト(新規ブックの追加②)"
        Dim objExcelApp As Excel.Application = Nothing          ' Excel.Application
        Dim objWorkbooks As Excel.Workbooks = Nothing           ' Excel.Workbooks
        Dim objWbk As Excel.Workbook = Nothing                  ' Excel.Workbook
        Dim objSh As Excel.Worksheet = Nothing                  ' Excel.Worksheet
        Dim objR As Excel.Range = Nothing                       ' Excel.Range
        '===========================================================================================
        ' Excel.Applicationのインスタンス生成(共通処理)
        If Not FP_GetExcelInstance(objExcelApp, cnsTitle) Then Exit Sub
        '-------------------------------------------------------------------------------------------
        ' ワークブックを追加
        objWorkbooks = objExcelApp.Workbooks
        objWbk = objWorkbooks.Add()
        With objWbk
            objSh = .Worksheets(1)
            With objSh
                objR = .Range("A1")
                objR.Value = "ABC"
                objR = .Range("A2")
                objR.Value = "DEF"
                objR = .Range("A3")
                objR.Value = "GHI"
            End With
            ' ワークシートを追加
            objSh = .Worksheets.Add()
            With objSh
                .Name = "HOGE"
                objR = .Range("A1")
                objR.Value = "JKL"
                objR = .Range("A2")
                objR.Value = "MNO"
                objR = .Range("A3")
                objR.Value = "PQR"
            End With
            .Saved = True
        End With
        '-------------------------------------------------------------------------------------------
        ' COM解放(まとめて)
        Call GP_ReleaseComObject(objR)
        Call GP_ReleaseComObject(objSh)
        Call GP_ReleaseComObject(objWbk)
        Call GP_ReleaseComObject(objWorkbooks)
        Call GP_ReleaseComObject(objExcelApp)
    End Sub
この記述でも、フォームは開いたままで Excelを終了させた場合に即座にEXCEL.EXEのプロセスも消失します。

[Button3] Excel参照テスト③
では、いちいちオブジェクト参照を変数に確保して解放しなくても良いのか、となります。
そこで、思い切ってExcelの「Application」以外は全て変数を作らずにWithステートメントで記述してしまいます。 これではどうでしょう。

    '***********************************************************************************************
    '* 処理名 :Button3_Click
    '* 機能  :ExcelのCOM参照テスト③ ※新規ブックの追加③
    '-----------------------------------------------------------------------------------------------
    '* 返り値 :(なし)
    '* 引数  :(既定)
    '-----------------------------------------------------------------------------------------------
    '* 作成日 :2009年03月01日
    '* 作成者 :井上 治
    '* 更新日 :2017年04月23日
    '* 更新者 :井上 治
    '* 機能説明:※WithステートメントでCOMオブジェクトを参照し、変数宣言を省略する
    '* 注意事項: ⇒[×]フォームを開いたままExcelを閉じてもプロセスが解放されずに残る
    '              但し、呼び出した元のフォームを閉じればプロセスも解放されるようだ
    '***********************************************************************************************
    Private Sub Button3_Click(ByVal sender As System.Object, _
                              ByVal e As System.EventArgs) Handles Button3.Click
        '-------------------------------------------------------------------------------------------
        Const cnsTitle = "ExcelのCOM参照テスト(新規ブックの追加③)"
        Dim objExcelApp As Excel.Application = Nothing          ' Excel.Application
        '===========================================================================================
        ' Excel.Applicationのインスタンス生成
        If Not FP_GetExcelInstance(objExcelApp, cnsTitle) Then Exit Sub
        '-------------------------------------------------------------------------------------------
        ' ワークブックを追加
        With objExcelApp.Workbooks
            With .Add()
                With .Worksheets(1)
                    .Range("A1").Value = "ABC"
                    .Range("A2").Value = "DEF"
                    .Range("A3").Value = "GHI"
                End With
                ' ワークシートを追加
                With .Worksheets.Add()
                    .Name = "HOGE"
                    .Range("A1").Value = "JKL"
                    .Range("A2").Value = "MNO"
                    .Range("A3").Value = "PQR"
                End With
                .Saved = True
            End With
        End With
        '-------------------------------------------------------------------------------------------
        ' COM解放(Applicationのみ)
        Call GP_ReleaseComObject(objExcelApp)
    End Sub
さすがにこれではダメでした。 Excelを閉じてもEXCEL.EXEのプロセスは残ってしまいます。 繰り返して操作するとEXCEL.EXEのプロセスは操作した数だけ増えていってしまいます。
但し、元のフォームを閉じるとEXCEL.EXEのプロセスは全部消失されます。

[Button4] Excel参照テスト④
念のため、①、②のように全てオブジェクト変数に確保してから、このページの最後に記載しているCOMオブジェクト解放の処置が正しく機能しているのか確認するため、この記述をわざと外してみます。

    '***********************************************************************************************
    '* 処理名 :Button4_Click
    '* 機能  :ExcelのCOM参照テスト④ ※新規ブックの追加④
    '-----------------------------------------------------------------------------------------------
    '* 返り値 :(なし)
    '* 引数  :(既定)
    '-----------------------------------------------------------------------------------------------
    '* 作成日 :2009年03月01日
    '* 作成者 :井上 治
    '* 更新日 :2017年04月23日
    '* 更新者 :井上 治
    '* 機能説明:※Excel本体のCOMオブジェクト以外の変数を解放をスキップする
    '        もしくはすべてのCOM解放を怠ってみる
    '* 注意事項: ⇒[×]フォームを開いたままExcelを閉じてもプロセスが解放されずに残る(当然!)
    '              但し、呼び出した元のフォームを閉じればプロセスも解放されるようだ
    '***********************************************************************************************
    Private Sub Button4_Click(ByVal sender As System.Object, _
                              ByVal e As System.EventArgs) Handles Button4.Click
        '-------------------------------------------------------------------------------------------
        Const cnsTitle = "ExcelのCOM参照テスト(新規ブックの追加④)"
        Dim objExcelApp As Excel.Application = Nothing          ' Excel.Application
        Dim objWorkbooks As Excel.Workbooks = Nothing           ' Excel.Workbooks
        Dim objWbk As Excel.Workbook = Nothing                  ' Excel.Workbook
        Dim objSh As Excel.Worksheet = Nothing                  ' Excel.Worksheet
        Dim objR As Excel.Range = Nothing                       ' Excel.Range
        '===========================================================================================
        ' Excel.Applicationのインスタンス生成(共通処理)
        If Not FP_GetExcelInstance(objExcelApp, cnsTitle) Then Exit Sub
        '-------------------------------------------------------------------------------------------
        ' ワークブックを追加
        objWorkbooks = objExcelApp.Workbooks
        objWbk = objWorkbooks.Add()
        With objWbk
            objSh = .Worksheets(1)
            With objSh
                objR = .Range("A1")
                objR.Value = "ABC"
                objR = .Range("A2")
                objR.Value = "DEF"
                objR = .Range("A3")
                objR.Value = "GHI"
            End With
            ' ワークシートを追加
            objSh = .Worksheets.Add()
            With objSh
                .Name = "HOGE"
                objR = .Range("A1")
                objR.Value = "JKL"
                objR = .Range("A2")
                objR.Value = "MNO"
                objR = .Range("A3")
                objR.Value = "PQR"
            End With
            .Saved = True
        End With
        '-------------------------------------------------------------------------------------------
        ' COM解放(怠る例)
        'Call GP_ReleaseComObject(objR)
        'Call GP_ReleaseComObject(objSh)
        'Call GP_ReleaseComObject(objWbk)
        'Call GP_ReleaseComObject(objWorkbooks)
        'Call GP_ReleaseComObject(objExcelApp)
    End Sub
当然ですが、Excelを閉じてもEXCEL.EXEのプロセスは残ってしまいます。 繰り返して操作するとEXCEL.EXEのプロセスは操作した数だけ増えていってしまいます。
但し、元のフォームを閉じるとEXCEL.EXEのプロセスは全部消失されます。

[Button5] Excel参照テスト⑤
あと、もう一点、「Application」「Workbooks」などは最初にしかその参照を利用しないので、先に解放させてしまって良いのかも確認しておきます。 その配下で参照を利用する「Workbook」「Worksheet」「Range」は、「親」のオブジェクトを解放した後でも利用できるのかを確認します。

    '***********************************************************************************************
    '* 処理名 :Button5_Click
    '* 機能  :ExcelのCOM参照テスト⑤ ※新規ブックの追加⑤
    '-----------------------------------------------------------------------------------------------
    '* 返り値 :(なし)
    '* 引数  :(既定)
    '-----------------------------------------------------------------------------------------------
    '* 作成日 :2009年03月01日
    '* 作成者 :井上 治
    '* 更新日 :2017年04月23日
    '* 更新者 :井上 治
    '* 機能説明:※ApplicationやWorkbooksだけ先に解放できるかの確認
    '* 注意事項: ⇒[○]フォームを開いたままでもExcelを閉じるとプロセスも解放される
    '***********************************************************************************************
    Private Sub Button5_Click(ByVal sender As System.Object, _
                              ByVal e As System.EventArgs) Handles Button5.Click
        '-------------------------------------------------------------------------------------------
        Const cnsTitle = "ExcelのCOM参照テスト(新規ブックの追加⑤)"
        Dim objExcelApp As Excel.Application = Nothing          ' Excel.Application
        Dim objWorkbooks As Excel.Workbooks = Nothing           ' Excel.Workbooks
        Dim objWbk As Excel.Workbook = Nothing                  ' Excel.Workbook
        Dim objSh As Excel.Worksheet = Nothing                  ' Excel.Worksheet
        Dim objR As Excel.Range = Nothing                       ' Excel.Range
        '===========================================================================================
        ' Excel.Applicationのインスタンス生成(共通処理)
        If Not FP_GetExcelInstance(objExcelApp, cnsTitle) Then Exit Sub
        '-------------------------------------------------------------------------------------------
        ' ワークブックを追加
        objWorkbooks = objExcelApp.Workbooks
        Call GP_ReleaseComObject(objExcelApp)                   ' ←先にApplicationを解放
        objWbk = objWorkbooks.Add()
        Call GP_ReleaseComObject(objWorkbooks)                  ' ←先にWorkbooksを解放
        With objWbk
            objSh = .Worksheets(1)
            With objSh
                objR = .Range("A1")
                objR.Value = "ABC"
                objR = .Range("A2")
                objR.Value = "DEF"
                objR = .Range("A3")
                objR.Value = "GHI"
            End With
            ' ワークシートを追加
            objSh = .Worksheets.Add()
            With objSh
                .Name = "HOGE"
                objR = .Range("A1")
                objR.Value = "JKL"
                objR = .Range("A2")
                objR.Value = "MNO"
                objR = .Range("A3")
                objR.Value = "PQR"
            End With
            .Saved = True
        End With
        '-------------------------------------------------------------------------------------------
        ' COM解放(Workbooks以外まとめて)
        Call GP_ReleaseComObject(objR)
        Call GP_ReleaseComObject(objSh)
        Call GP_ReleaseComObject(objWbk)
    End Sub
この状態でも、フォームは開いたままで Excelを終了させた場合に即座にEXCEL.EXEのプロセスも消失します。

[Button6] Excel参照テスト⑥
では、解放を行なうオブジェクトを制限してみて、全てのオブジェクトをCOMオブジェクトとして解放しなければならないのかを確認してみます。
以下の記述はここまで来るまで変更しながら試したものの最終形です。「Workbook」以下はオブジェクト変数に確保せず、Withステートメントで処理しています。

    '***********************************************************************************************
    '* 処理名 :Button6_Click
    '* 機能  :ExcelのCOM参照テスト⑥ ※新規ブックの追加⑥
    '-----------------------------------------------------------------------------------------------
    '* 返り値 :(なし)
    '* 引数  :(既定)
    '-----------------------------------------------------------------------------------------------
    '* 作成日 :2009年03月01日
    '* 作成者 :井上 治
    '* 更新日 :2017年04月23日
    '* 更新者 :井上 治
    '* 機能説明:※⑤からWorkbookオブジェクト以下のオブジェクトをWith参照にしてみる
    '        さらにInteriorやPageSetupも操作してみる
    '* 注意事項: ⇒[○]フォームを開いたままでもExcelを閉じるとプロセスも解放される
    '         つまり、シート内の個々のオブジェクトをいちいち解放しなくても可!?
    '***********************************************************************************************
    Private Sub Button6_Click(ByVal sender As System.Object, _
                              ByVal e As System.EventArgs) Handles Button6.Click
        '-------------------------------------------------------------------------------------------
        Const cnsTitle = "ExcelのCOM参照テスト(新規ブックの追加⑥)"
        Dim objExcelApp As Excel.Application = Nothing          ' Excel.Application
        Dim objWorkbooks As Excel.Workbooks = Nothing           ' Excel.Workbooks
        '===========================================================================================
        ' Excel.Applicationのインスタンス生成
        If Not FP_GetExcelInstance(objExcelApp, cnsTitle) Then Exit Sub
        '-------------------------------------------------------------------------------------------
        ' ワークブックを追加
        objWorkbooks = objExcelApp.Workbooks
        With objWorkbooks.Add()
            With .Worksheets(1)
                .Range("A1").Value = "ABC"
                .Range("A2").Value = "DEF"
                .Range("A3").Value = "GHI"
                .PageSetup.PrintArea = .UsedRange.Address
            End With
            ' ワークシートを追加
            With .Worksheets.Add()
                .Name = "HOGE"
                .Range("A1").Value = "JKL"
                .Range("A2").Value = "MNO"
                .Range("A3").Value = "PQR"
                .Range("A1:A3").Interior.ColorIndex = 5
            End With
            .Saved = True
        End With
        '-------------------------------------------------------------------------------------------
        ' COM解放
        Call GP_ReleaseComObject(objWorkbooks)
        Call GP_ReleaseComObject(objExcelApp)
    End Sub
この状態でも、フォームは開いたままで Excelを終了させた場合に即座にEXCEL.EXEのプロセスも消失します。

これらのテストからの結論
.NETでは、COMとの相性が悪いので Excelなどを利用すること自体をやめた方が良いなどと開発サイドの意見として書いているサイトもあるようですが、ここまでの結果を見ると「そうでもない」ようです。
結局、COMオブジェクトを解放しないとプロセス残存になってしまうのは、上記のようなテストで見る限りでは、「Application」と「Workbooks」だけなのです。
配下の「Workbook」「Worksheets」「Worksheet」「Range」「PageSetup」「Interior」などのオブジェクトは、オブジェクト変数に参照を確保して解放せずに放置しても、オブジェクト変数を使わずにWithステートメントで処理してしまっても、EXCEL.EXEのプロセス残存には影響しないということのようです。
なお、ここでは確認していませんが「Windows」も該当なのかも知れません。ですが実践では「ActiveWindow」を解放せずに利用していても問題なくプロセスは解放できています。
但し、環境面での確認が限られいるので不足な点があるかも知れません。何か他の条件で確認できる方があれば「意見・質問」でお知らせいただければと思います。

※今回、MOUGなどでお世話になったAbyssさん(HN)から教えていただいた「GetTypeFromProgID」「Activator.CreateInstance」を使ってExcelインスタンスを取得する方法を採り入れています。 (この方法の是非についてはよく解っていませんが、CreateObjectは旧VBの互換で残されているのでしょう。変更してから半年くらい運用していますが何も問題は起きていません。

COMオブジェクトの解放についても、1カウントごとにデクリメントするような記述でなくて、一気に解放できるようなので下記の記述を使っています。
Excel.Applicationのインスタンス生成(FP_GetExcelInstance)」の方は、上記の各ボタンからの処理先頭で共通に呼び出されているExcel.Applicationを取得する処理です。

    '***********************************************************************************************
    '   ■■■ 共通サブ処理 ■■■
    '***********************************************************************************************
    '* 処理名 :FP_GetExcelInstance
    '* 機能  :Excel.Applicationのインスタンス生成
    '-----------------------------------------------------------------------------------------------
    '* 返り値 :処理成否(Boolean)
    '* 引数  :Arg1 = Excel.Application(Object)        ※Ref参照
    '*      Arg2 = 処理タイトル(String)
    '-----------------------------------------------------------------------------------------------
    '* 作成日 :2009年03月01日
    '* 作成者 :井上 治
    '* 更新日 :2017年04月23日
    '* 更新者 :井上 治
    '* 機能説明:
    '* 注意事項:
    '***********************************************************************************************
    Private Function FP_GetExcelInstance(ByRef objExcelApp As Excel.Application, _
                                         ByVal strTitle As String) As Boolean
        '-------------------------------------------------------------------------------------------
        Dim typType As Type = Type.GetTypeFromProgID(g_cnsExcelApplication) ' Type
        ' 取得失敗
        If typType Is Nothing Then
            MessageBox.Show("Excelがインストールされていません。", strTitle)
            Return False
        End If
        objExcelApp = Activator.CreateInstance(typType)
        ' Microsoft Excelを表示
        objExcelApp.Visible = True
        Return True
    End Function

    '***********************************************************************************************
    '* 処理名 :GP_ReleaseComObject
    '* 機能  :COMオブジェクトの解放
    '-----------------------------------------------------------------------------------------------
    '* 返り値 :(なし)
    '* 引数  :Arg1 = 解放するCOMオブジェクト(Object)
    '-----------------------------------------------------------------------------------------------
    '* 作成日 :2009年03月01日
    '* 作成者 :井上 治
    '* 更新日 :2009年03月01日
    '* 更新者 :井上 治
    '* 機能説明:
    '* 注意事項:
    '***********************************************************************************************
    Private Sub GP_ReleaseComObject(ByRef objCOM As Object)
        '-------------------------------------------------------------------------------------------
        ' 明示的にCOMオブジェクトへの参照を解放する
        Try
            ' ランタイム呼び出し可能ラッパーの参照カウントをデクリメント
            If ((objCOM IsNot Nothing) AndAlso (Marshal.IsComObject(objCOM))) Then
                Marshal.FinalReleaseComObject(objCOM)
            End If
        Finally
            ' 参照を解除する
            objCOM = Nothing
        End Try
    End Sub

エンドユーザーはExcelに手慣れている場合が多くExcelへの出力を要求します。 .NETのアプリだからといってExcelを否定的に扱うのは単に開発側の言い分であって、そもそも不可能ではないわけです。 VB.NETではCOM利用に否定的な見解の開発者も多ようですが、ユーザーサイドに立って考えてみてはいかがでしょうか。
フォーム画面が崩れる時は...   近年の「高DPIノートPC」をWindows7以前のバージョンで使用している場合は、 フォームやコントロールの大きさが変わらないのに文字だけ大きくなってしまうのでフォーム画面が崩れた状態に表示されてしまいます。
Windows8.1以降ではOS側がスクリーンごとのDPI設定を含めて補正してしまうので問題ないのですが、 Windows7以前ではプログラム側で対策を講じる必要があります。
本件についてはDPI制御の問題」をご覧下さい。