kage

プログラマーをしてます。C++, C#, Java など Windows メイン。今はもっぱらC#。C++/CLIも面白くていじりだしてます。

8月 062012
 

はじめまして、kageと申します。
このブログではプログラミング、主に.NetやC#についての話題を取り上げようと思います。

C#で開発していて結構たいへんなのはExcelなどCOMのオブジェクトを扱うときです。
スペースがないので書きませんが旧来のVB6と違って非常に手間です。
しかし Visual Studio 2010 が出てそれが少し緩和されました。
これに関連する機能としてC#4.0では
1.dynamic型の導入
2.オプション引数の導入
です。
これら機能でかなりExcelなどのCOMオブジェクトを扱うのが楽になりましたがCOMオブジェクトの開放はまだプログラムで行わないといけません。
本当は.NET Framework では RCW(Runtime Callable Wrapper) がオブジェクトのファイナライザを呼び出すときにCOMオブジェクトの開放を行なってくれますが、ファイナライザはいつ呼び出されるかわからないのでCOMオブジェクトの寿命を自分で管理したい時は意識的にCOMオブジェクトの開放を行う必要があります。

このCOMオブジェクトの開放は非常に面倒ですがこれを楽にすることは出来ないかとネットを漁ったところ
C#からExcelを操作するライブラリ
というページを見つけました。

このページのアイデアはCOMオブジェクトの開放を行う基本クラスを作成し、この基本クラスから派生させたExcelのCOMのラッパークラスのライブラリを作るをことでCOMオブジェクトの開放をプログラム側で意識する必要をなくすというものです。
非常に秀逸なアイデアですのでこのアイデアを用いて C# 4.0 で導入された dynamic 型を利用したクラス ComObjectBase を作ってみました。

ComObjectBase.cs

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;

namespace ComWrapper
{
    /// <summary>
    /// 全てのCOMラッパークラスの抽象基本クラス.
    /// COMオブジェクトを管理する
    /// </summary>
    public abstract class ComObjectBase : IDisposable
    {
        #region フィールド変数
        /// <summary>
        /// COMオブジェクト
        /// </summary>
        protected readonly object comObject;
        /// <summary>
        /// dynamic型のCOMオブジェクト
        /// </summary>
        protected readonly dynamic comDynamic;
        /// <summary>
        /// 親オブジェクト
        /// </summary>
        protected readonly ComObjectBase parent;
        /// <summary>
        /// 子オブジェクトのリスト
        /// </summary>
        private readonly List<ComObjectBase> childs;
        /// <summary>
        /// Disposeされたことを示すフラグ
        /// </summary>
        private bool isDisposed;
        #endregion

        #region コンストラクタ
        /// <summary>
        /// ComObjectBaseのコンストラクタ
        /// </summary>
        /// <param name="comObject">COMオブジェクト</param>
        /// <param name="parent">親オブジェクト</param>
        public ComObjectBase(object comObject, ComObjectBase parent)
        {
            this.parent = parent;
            if (this.parent != null)
                this.parent.AddChild(this);
            this.comObject = comObject;
            this.comDynamic = comObject;
            this.childs = new List<ComObjectBase>();
            this.isDisposed = false;
        }
        #endregion

        #region Dispose・デストラクタ
        /// <summary>
        /// Disposeメソッド
        /// </summary>
        /// <param name="disposing">マネージリソースの開放を行うフラグ</param>
        protected virtual void Dispose(bool disposing)
        {
            if (!this.isDisposed)
            {
                this.isDisposed = true;

                if (disposing)
                {
                    // マネージリソースの開放

                    // 子オブジェクトの開放
                    this.childs.ForEach((item) => item.Dispose());
                    this.childs.Clear();

                    // COMオブジェクトの参照カウントをデクリメント
                    Marshal.ReleaseComObject(this.comObject);

                }

                // アンマネージリソースの開放

            }
        }
        /// <summary>
        /// Disposeメソッド
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            System.GC.SuppressFinalize(this);
        }
        #endregion

        #region プライベートメソッド
        /// <summary>
        /// 子オブジェクトを追加する
        /// </summary>
        /// <param name="childObject">子オブジェクト</param>
        private void AddChild(ComObjectBase child)
        {
            this.childs.Add(child);
        }
        #endregion
    }
}

このクラスで先のページの基本クラスと違うのはdynamic型のprotectedなメンバ変数を追加してることです。
この基本抽象クラスComObjectBaseはコンストラクタで親オブジェクトを指定し、親オブジェクト側では登録された子オブジェクトをリストで管理し、Disposeメソッド内で子オブジェクトのDisposeメソッドの呼び出し、および自分自身管理しているCOMオブジェクトの開放を行なっています。
ここで注意しないといけないのはCOMの参照カウントは.NET4ではマネージリソースとして扱わないといけないということです。.NET4以前はアンマネージリソースとして扱っても大丈夫でした。.NET4でRCWでのCOMオブジェクトの動作が変わったようです。詳しくは
Marshal.ReleaseComObjectは危険な場合がある
を参照してください。
もしアンマネージリソースとしてファイナライズ時にCOMの開放を行うようにすると「COMオブジェクトは、すでにRCWから解放されているため使用できません。」というメッセージとともにInvalidComObjectExceptionがスローされることがあります。この例外はオブジェクトのファイナライザが呼び出された後、RCWがCOMの参照カウントをデクリメントするときに発生するため簡単に捕捉できません。次回発表するExcelのラッパークラスで動作確認をしているとこの例外が発生して先の
Marshal.ReleaseComObjectは危険な場合がある
のページを見るまで原因がわかりませんでした。Marshal.ReleaseComObjectをDispose(true)の時しか呼び出さないようにして例外が発生しなくなりました。(実はこのInvalidComObjectException、現在は発生しません。.NET4 が修正されたのでしょうか?)

このComObjectBaseクラスはExcelだけでなくすべてのCOMオブジェクトで使えるのでComWrapperというアセンブリにしました。
次回はこのアセンブリを利用してExcelのラッパークラスのライブラリを作ってみます。