ようこそ。睡眠不足なプログラマのチラ裏です。

(続)リフレクションを利用したレイトバインディングでExcelファイルを開く

Excel関連でググってみると、「Excelのプロセスが残ってしまう・・・。」といった現象に陥ってる人が多いように思う。


Excelのプロセスが残らないようにするには、利用したCOMオブジェクトの参照を適切に解放してあげる必要がある。
例えば、レイトバインディングに利用したobject型の変数を複数回使用すると、
参照カウントが通常より多くカウントされてしまい、通常の解放処理では全ての参照を解放できない場合がある。
これは、ランタイム呼び出し可能ラッパーが、COM インターフェイス ポインタが割り当てられるたびに
インクリメントされる参照カウントを保持してしまうのが原因だ。


System.Runtime.InteropServices.Marshal.ReleaseComObjectメソッドは、
ランタイム呼び出し可能ラッパーの参照カウントをデクリメントしてくれるので、
参照カウントが0(ゼロ)に到達するまで、複数回呼び出してあげると万事解決する。*1


ランタイム呼び出し可能ラッパーの参照カウントが0(ゼロ)になると、
ランタイムはアンマネージ COM オブジェクトのすべての参照が解放される。
この後でオブジェクトを使用しようとすると System.NullReferenceExceptionの例外がスローされる。


以下、ランタイム呼び出し可能ラッパーの参照カウントをデクリメントする雑なサンプル。

using System;
using System.Windows.Forms;
using System.Reflection;

namespace WindowsApplication1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        private void button2_Click(object sender, EventArgs e)
        {
            object xlsApp = null;
            object xlsBooks = null;
            object xlsBook = null;
            object xlsSheets = null;
            object xlsSheet = null;
            object xlsRange = null;
            object xlHyperLinks = null;
            object xlHyperlink = null;
            
            try
            {
                // Excelファイルのパス
                string xlsPath = @"C:\teeestoooo.xls";

                // Excelのクラスのタイプとインスタンスを取得する 
                xlsApp = CreateObject("Excel.Application");

                //ワークブックコレクションオブジェクト
                xlsBooks = xlsApp.GetType().InvokeMember("Workbooks", BindingFlags.GetProperty, null, xlsApp, null);

                //Excelファイルのオープン
                xlsBook = xlsBooks.GetType().InvokeMember(
                                      "Open", BindingFlags.InvokeMethod, null,
                                      xlsBooks, new object[] { 
                                                               xlsPath
                                                             , Type.Missing
                                                             , Type.Missing
                                                             , Type.Missing
                                                             , Type.Missing
                                                             , Type.Missing
                                                             , Type.Missing
                                                             , Type.Missing
                                                             , Type.Missing
                                                             , Type.Missing
                                                             , Type.Missing
                                                             , Type.Missing
                                                             , Type.Missing 
                                                             });

                xlsSheets = xlsBook.GetType().InvokeMember("WorkSheets", BindingFlags.GetProperty, null, xlsBook, null);
                xlsSheet = xlsSheets.GetType().InvokeMember("Item",BindingFlags.GetProperty,null,xlsSheets,new object[]{1});
                xlsRange = xlsSheet.GetType().InvokeMember("Range",BindingFlags.GetProperty, null, xlsSheet,  new object[] { "A1" });
                string a1 = (string)xlsRange.GetType().InvokeMember("Value", BindingFlags.GetProperty, null, xlsRange, null);
                MessageBox.Show(a1);

                xlsRange = xlsSheet.GetType().InvokeMember("Range", BindingFlags.GetProperty, null, xlsSheet, new object[] { "B1" });
                string b1 = (string)xlsRange.GetType().InvokeMember("Value", BindingFlags.GetProperty, null, xlsRange, null);
                
                MessageBox.Show(b1);

                xlHyperLinks = xlsSheet.GetType().InvokeMember("Hyperlinks", BindingFlags.GetProperty, null, xlsSheet, null);

                xlHyperLinks = xlsSheet.GetType().InvokeMember("Hyperlinks", BindingFlags.GetProperty, null, xlsSheet, null);

                xlHyperlink = xlHyperLinks.GetType().InvokeMember("Add",BindingFlags.InvokeMethod,null,xlHyperLinks,new object[]{xlsRange,b1});

                xlsBook.GetType().InvokeMember("Save",BindingFlags.InvokeMethod,null,xlsBook,null);

            }
            finally
            {
                if (xlHyperlink != null) { MarshalReleaseComObject(ref xlHyperlink);}
                if (xlHyperLinks != null) { MarshalReleaseComObject(ref xlHyperLinks);}
                if (xlsRange != null) { MarshalReleaseComObject(ref xlsRange);}
                if (xlsSheet != null) { MarshalReleaseComObject(ref xlsSheet); }
                if (xlsSheets != null) { MarshalReleaseComObject(ref xlsSheets); }
                
                if (xlsBook != null){
                    xlsBook.GetType().InvokeMember("Close", BindingFlags.InvokeMethod, null, xlsBook, null);
                }
                if (xlsBook != null) { MarshalReleaseComObject(ref xlsBook); }
                if (xlsBooks != null) { MarshalReleaseComObject(ref xlsBooks); }
                
                if (xlsApp != null){
                    xlsApp.GetType().InvokeMember("Quit", BindingFlags.InvokeMethod, null, xlsApp, null);
                }
                if (xlsApp != null) { MarshalReleaseComObject(ref xlsApp); }
            }
        }

        /// <summary>
        /// COMオブジェクトへの参照を作成および取得する
        /// </summary>
        /// <param name="progId">作成するオブジェクトのプログラムID</param>
        /// <param name="serverName">
        /// オブジェクトが作成されるネットワーク サーバーの名前
        /// </param>
        /// <returns>作成されたCOMオブジェクト</returns>
        public static object CreateObject(string progId, string serverName)
        {
            Type t;
            if (serverName == null || serverName.Length == 0)
                t = Type.GetTypeFromProgID(progId);
            else
                t = Type.GetTypeFromProgID(progId, serverName, true);
            return Activator.CreateInstance(t);
        }

        /// <summary>
        /// COMオブジェクトへの参照を作成および取得する
        /// </summary>
        /// <param name="progId">作成するオブジェクトのプログラムID</param>
        /// <returns>作成されたCOMオブジェクト</returns>
        public static object CreateObject(string progId)
        {
            return CreateObject(progId, null);
        }

        /// <summary>
        /// COMオブジェクトの参照カウントが0になるまで
        /// Marshal.ReleaceComObjectによるCOMオブジェクトの解放処理を行います。
        /// </summary>
        /// <param name="objCom">COMオブジェクト</param>
        private void MarshalReleaseComObject(ref object objCom){
            try{
                int i = 1;
                if (objCom != null &&  System.Runtime.InteropServices.Marshal.IsComObject(objCom))
                {
                    //参照カウントが0より大きい間・・・
                    do {
                        //ランタイム呼び出し可能ラッパーの参照カウントをデクリメント
                        i = System.Runtime.InteropServices.Marshal.ReleaseComObject(objCom);
                    } while (i > 0);
                }
            }
            finally{    
                objCom = null;    
            }
        }
    }
}

*1:同じ COM インターフェイスがアンマネージ コードからマネージ コードに複数回渡された場合、ラッパーの参照カウントは毎回インクリメントされ、System.Runtime.InteropServices.Marshal.ReleaseComObjectメソッド を呼び出すと、残りの参照の数が返されます。