« マネージドDLLの動的リンク(3) | トップページ | Sandcastle 日本語ローカライズ情報 »

2010年11月16日 (火)

マネージドDLLの動的リンク(4)[最終回]

前回の最後で触れた様に、今回は、

  1. 「マネージドDLLの動的リンク(2)」で使用したハイブリッド型にインターフェースを加えて使用する方法
  2. 「マネージドDLLの動的リンク(3)」で使用したサブドメイン利用型でインターフェースを使用しない方法

について、研究します。

1.ハイブリッド型にインターフェースを加えて使用する方法

「マネージドDLLの動的リンク(2)」では、動的リンクにする事により発生する『型の動的利用』を極力少なくする事を目的として、ラッパークラスを作成しました。ラッパークラスを作成するなら、ついでにインターフェースを作成・継承する事も出来ます。アプリケーション構成上は、以下のようにほとんど、「マネージドDLLの動的リンク(3)」で使用したサブドメイン利用型と一緒です。

サブドメインを使用しないので、ラッパークラスが、MarshalByRefObject を継承しない事と、SerializableAttribute 属性を持たせる必要がない事位の違いしかありません。
では、「マネージドDLLの動的リンク(2)」で使用したプログラムソースを基にして、インターフェースを加えてみましょう。

1-1.インターフェースの定義

新たに作成するインターフェースは、以下の通りとなります。

using System;
using System.Windows.Forms;

namespace Acha_ya.InterfaceClass
{
    public interface ISharpZipLibWrapper
    {
        bool Enabled
        {
            get;
        }
        string ZipFileName
        {
            get;
            set;
        }
        void FileList(ListBox listBox);
        void AddMember(string[] addFiles);
        void DeleteMember(string member);
        void Extract(string meltFolder);
        void CreateZip(string archiveFile, string compFolder);
    }
}

「マネージドDLLの動的リンク(3)」で使用したインターフェースとの違いは、Enabled プロパティを(一応)装備しているので、それを加えている事と、FileList メソッドの引数が ListBox クラスである事だけです。ラッパークラスに SerializableAttribute 属性を持たせなければ、従来の ListBox をそのまま引数として使えるからです。

1-2.ラッパークラスの定義

ここは、1箇所変更するだけです。クラスの宣言部でインターフェースを継承する様に追加指定を行なっています。

public class SharpZipLibWrapper : Acha_ya.InterfaceClass.ISharpZipLibWrapper

1-3.アプリケーションでのラッパークラス呼び出し

インターフェースを利用して、ここは簡単に記述出来るようになりますが、「マネージドDLLの動的リンク(2)」では、ブログに載せたコードとそこからダウンロード出来るサンプルコード(ブログのコードを最適化)は違っている為、どちらで比較するするか、という問題がありますね。「マネージドDLLの動的リンク(2)」の記事作成時点では、(3)を最終回にする予定だったので、許して下さい。今回の記事は、(3)の記事を作成中に、もっと探求したくなった為に書いている予定外の記事なのです。
ともかく、ここはコード比較に混乱がおきない様、ここだけ新旧双方のコードを載せます。

※インターフェースを加えない従来のハイブリッド型の書庫一覧(FileList)メソッド

/// <summary>
/// ラッパークラスの動的リンクによる書庫一覧取得・表示
/// </summary>
/// <param name="archiveFile">ZIP書庫ファイルのフルパス</param>
private void FileList3(string archiveFile)
{
    Type sharpZipLibWrapper = zip.GetType("Acha_ya.SampleClass.SharpZipLibWrapper", true, false);
    object zf = sharpZipLibWrapper.InvokeMember(null, BindingFlags.CreateInstance, null, null,
        new object[] { archiveFile });
    sharpZipLibWrapper.InvokeMember("FileList", BindingFlags.InvokeMethod, null, zf,
        new object[] { listBox1 });
}

※インターフェースを加えた今回の書庫一覧(FileList)メソッド

/// <summary>
/// ラッパークラスの動的リンクによる書庫一覧取得・表示
/// </summary>
/// <param name="archiveFile">ZIP書庫ファイルのフルパス</param>
private void FileList3(string archiveFile)
{
    Type sharpZipLibWrapper = zip.GetType("Acha_ya.SampleClass.SharpZipLibWrapper", true, false);
    object zf = sharpZipLibWrapper.InvokeMember(null, BindingFlags.CreateInstance, null, null,
        new object[] { archiveFile });
    InterfaceClass.ISharpZipLibWrapper zipLib = zf as InterfaceClass.ISharpZipLibWrapper;
    zipLib.FileList(listBox1);
}

書庫一覧以外のその他のメソッドもほぼ同じ様な形になるので、比較は省略します。
正直、どちらも大差ないですね。インスタンス作成は InvokeMember でやらないといけないので、特にメリットはない様な気がします。しかしラッパークラスを使用しても、いくつかのプロパティ設定が必要だとか、メソッドの引数に ref / out キーワード指定を使用したものを使いたい場合は、InvokeMember での使用はやや面倒になります。こういった場合には、今回のインターフェース追加型の検討をするのも「あり」ではないかと思います。

2.サブドメイン利用型でインターフェースを使用しない方法

「マネージドDLLの動的リンク(3)」で使用したサブドメイン利用型の動的リンクでは、元ネタの関係もあって、インターフェース継承を行なっていました。では、これが必須なのかというと、別にそういう訳ではありません。そこで、サブドメイン利用型でインターフェースを使用しないコードも作成してみました。
まずは、アプリケーション構成図から示します。

サブドメイン利用上、ラッパークラスが MarshalByRefObject を継承する事と、SerializableAttribute 属性を持たせる必要がある事を除けば、「マネージドDLLの動的リンク(2)」の構成と変わりません。

2-1.ラッパークラスの定義

ここは、1箇所変更するだけです。クラスの宣言部でインターフェースを継承しない為、その部分の記述を削除するだけです。

public class SharpZipLibWrapper : MarshalByRefObject//, Acha_ya.InterfaceClass.ISharpZipLibWrapper

また、インターフェースは使用しなくなるので、ここでソリューションからインターフェースのプロジェクトを削除します。

2-2.アプリケーションでのラッパークラス呼び出し

2-2-1.書庫へのメンバ追加

この部分のプログラムは以下のようになります。ドメイン作成(事実上のDLL動的リンク)やドメイン開放(事実上のDLLのリンク開放)を行なっているので、ちょっと記述量が多いですが、以前のコードを削除した部分を赤、追加した行を水色で表示してみました。つまり、色付きの部分が変更箇所です。

/// <summary>
/// 動的リンクによるZIP書庫へのメンバ追加
/// </summary>
/// <param name="archiveFile">ZIP書庫ファイルのフルパス</param>
/// <param name="addFiles">追加対象となるファイルパスの配列</param>
private void AddMember2(string archiveFile, string[] addFiles)
{
    AppDomain appDomain = null;
    System.Runtime.Remoting.ObjectHandle handle = null;
    //ISharpZipLibWrapper zip = null;
    try
    {
        appDomain = AppDomain.CreateDomain("ZipManagedLink");
        handle = appDomain.CreateInstance("SharpZipLibWrapper",
            "Acha_ya.SampleClass.SharpZipLibWrapper");
        //zip = handle.Unwrap() as ISharpZipLibWrapper;
        object zip = handle.Unwrap();
        Type sharpZipLibWrapper = zip.GetType();
        // 実質的なAddMember2処理部の始まり
        //zip.ZipFileName = archiveFile;
        //zip.AddMember(addFiles);
        sharpZipLibWrapper.InvokeMember("ZipFileName", BindingFlags.SetProperty,
            null, zip, new object[] { archiveFile });
        sharpZipLibWrapper.InvokeMember("AddMember", BindingFlags.InvokeMethod,
            null, zip, new object[] { addFiles });
        // 実質的なAddMember2処理部の終わり
    }
    catch (FileNotFoundException e)
    {
        MessageBox.Show("動的リンクに失敗しました", e.FileName,
            MessageBoxButtons.OK, MessageBoxIcon.Stop);
    }
    finally
    {
        if (appDomain != null)
            AppDomain.Unload(appDomain);
    }
}

インターフェースを使わない為、InvokeMember を使用する事になりますが、それ以外は特に変わる所はありません。

2-2-2.書庫のメンバ一覧取得

これも基本的には、書庫へのメンバ追加と同じです。しかし、FileList メソッドの引数は、out 指定付きなので、相応の対応が必要です。

/// <summary>
/// 動的リンクによる書庫一覧取得・表示
/// </summary>
/// <param name="archiveFile">ZIP書庫ファイルのフルパス</param>
private void FileList2(string archiveFile)
{
    AppDomain appDomain = null;
    System.Runtime.Remoting.ObjectHandle handle = null;
    //ISharpZipLibWrapper zip = null;
    try
    {
        StringCollection nameList = null;
        appDomain = AppDomain.CreateDomain("ZipManagedLink");
        handle = appDomain.CreateInstance("SharpZipLibWrapper",
            "Acha_ya.SampleClass.SharpZipLibWrapper");
        //zip = handle.Unwrap() as ISharpZipLibWrapper;
        object zip = handle.Unwrap();
        Type sharpZipLibWrapper = zip.GetType();
        // 実質的なFileList2処理部の始まり
        //zip.ZipFileName = archiveFile;
        //zip.FileList(out nameList);
        sharpZipLibWrapper.InvokeMember("ZipFileName", BindingFlags.SetProperty,
            null, zip, new object[] { archiveFile });
        ParameterModifier pm = new ParameterModifier(1);
        pm[0] = true;
        object[] args = { nameList };
        sharpZipLibWrapper.InvokeMember("FileList", BindingFlags.InvokeMethod,
            null, zip, args, new ParameterModifier[] { pm }, null, null);
        nameList = (StringCollection)args[0];
        listBox2.Items.Clear();
        foreach (string name in nameList)
            listBox2.Items.Add(name);
        // 実質的なFileList2処理部の終わり
    }
    catch (FileNotFoundException e)
    {
        MessageBox.Show("動的リンクに失敗しました", e.FileName,
            MessageBoxButtons.OK, MessageBoxIcon.Stop);
    }
    finally
    {
        if (appDomain != null)
            AppDomain.Unload(appDomain);
    }
}

InvokeMember による呼び出しの場合、out キーワードか ref キーワードかの区別がない様です。正確には、out キーワード相当の指定は出来ない様です。この例では呼び出し先で new StringCollection() を行なっているので、初期値 null を指定していますが、これはそうしないとコンパイラに怒られるからです。
それはともかく、この場合はちょっと複雑な記述になりますね。これなら、インターフェース付きで定義するのも「あり」ではないでしょうか。

3.まとめ

以上、いろいろな観点からの動的リンク手法について検討してみましたが、一覧形式で特徴をまとめてみました。

 Assembly.LoadFileによる動的リンク
(一般的なマネージドDLL動的リンク方式)
AppDomain.CreateInstanceによる動的リンク
(サブドメインを利用したマネージドDLL動的リンク方式)
長所
  • DLLが動的リンクしやすい設計(ラッパークラスの作成の必要がない構成)になっていれば、静的リンクからの変更は容易である。またこの場合、アプリケーションファイルの構成も変更がない。
  • 動的リンク対象のDLLを作成する際に、任意のクラスを継承する事が可能である。
  • AppDomain.Unloadにより、動的リンクを開放出来る。
短所
  • 一度リンクしたら開放は出来ない。
  • 呼び出すDLLは、SerializableAttribute 属性を持つ必要があるので、その制約を受ける。
  • MarshalByRefObject クラスを継承する必要があるので、動的リンク対象のDLLは、任意のクラスを継承する事が出来ない。
  • MarshalByRefObject クラスを継承する必要があるので、既存のDLLの場合はソースを書き換えるか、ラッパークラスを作成する必要がある。

また、インターフェース定義の有無による特徴については、以下のようになります。

 インターフェース定義無しで使用インターフェース定義付きで使用
長所
  • DLLが動的リンクしやすい設計になっているか、ラッパークラスを作成する事で、比較的容易にアクセスする事も可能になる。
  • 静的リンク時と同様の記述でプロパティやメソッドを利用出来る。
短所
  • 常に型の動的呼び出しによる方法を使用する為、使用形態により、プログラムコードが複雑になり過ぎる場合がある。
  • 将来の機能拡張に制約を受ける可能性が高い。
  • ラッパークラスを作成したとしても、これとは別のDLLとしてインターフェースを作成する必要がある。その為、アプリケーション構成が複雑になる。
  • 既存のDLLの場合は、インターフェース継承にソースを書き換えるか、ラッパークラスを作成する必要がある。

ここまでやってきてから言うのも気がひけますが、マネージドDLLについては、一般論として、動的リンクにした場合のコストが大きいといえます。動的リンクしたい理由にもよるでしょうが、場合によっては諦めるのが賢明かもしれません。
例えば、アプリケーションを起動したまま、DLLは入れ替えたいといった場合であれば、シャドウコピーが可能ならそれを利用する手もあります。また、DLLがオプションであり、DLLの存在有無に関係なくアプリケーションを使用したい場合は、必ずしも動的リンクする必要はないと思います。DLLがない状態で、静的リンク形態のプログラムを実行しても、該当するDLLクラスを利用したコードを実行した時点で例外が発生するだけです。その為、その時に発生する例外を catch して対策を施す様にアプリケーションを設計すれば、それでも問題なさそうな気がします。

これらを総合的に考慮した上で、動的リンクするのかしないのか、するならどの形式にするかを判断するのが良いでしょう。

最後に、今回の記事に使用したサンプルソース一式をCAB形式で圧縮して、ここに置いておきます。
「DLLLink4.cab」をダウンロード


« マネージドDLLの動的リンク(3) | トップページ | Sandcastle 日本語ローカライズ情報 »

C#研究」カテゴリの記事

コメント

コメントを書く

コメントは記事投稿者が公開するまで表示されません。

(ウェブ上には掲載しません)

トラックバック


この記事へのトラックバック一覧です: マネージドDLLの動的リンク(4)[最終回]:

« マネージドDLLの動的リンク(3) | トップページ | Sandcastle 日本語ローカライズ情報 »