(続)リフレクションを利用したレイトバインディングで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メソッド を呼び出すと、残りの参照の数が返されます。