« マネージドDLLの動的リンク(2) | トップページ | マネージドDLLの動的リンク(4)[最終回] »

2010年11月14日 (日)

マネージドDLLの動的リンク(3)

マネージドDLLの動的リンク(2)の続きです。
前回、ハイブリッドタイプにする事により、動的リンクを実用的な範囲にする事ができましたが、今回は別の動的リンクによる方法を研究し、どちらが優れているかを検討したいと思います。

まずはじめに、今回の動的リンクの方法の元ネタとなったところを示します。
@IT > 旧@IT会議室 > Insider.NET > 動的DLLの解放について
http://www.atmarkit.co.jp/bbs/phpBB/viewtopic.php?topic=46665&forum=7

ここで示されているプログラムはVB.NETですが、リンクしたDLLを開放する事が可能になります。前回示したAssembly.LoadFileによるリンクでは、一度リンクしたらアプリケーション終了まで開放出来ませんから、その点でメリットを感じる場合もあるでしょう。

まず、この方法によるプログラム構成は、下図の様になります。前回のハイブリッドタイプからは、1つ増えただけなのですが、ちょっと複雑に感じますね。

ラッパークラスで利用したいメソッドは全て、別DLLのインターフェースとして定義します。アプリケーション側では、このインターフェースを見て、メソッドの型情報を取得します。ラッパークラスは、アプリケーションのサブドメイン環境下で動的リンクするので、必ず MarshalByRefObject を継承します。そしてもちろん、前述のインターフェースも継承します。

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

まず新たに定義する事になるのがインターフェースです。しかし、実態は前回のラッパークラスの公開メソッドとプロパティを定義する様なものですから、中身は簡単です。

using System;
using System.Collections.Specialized;

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

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

これも基本的には、前回のラッパークラスと同じ様な定義になります。前述の通り、MarshalByRefObject と、1.で定義したインターフェースを継承する事と、必ず SerializableAttribute の属性を持たせる点に注意して下さい。
こちらのリストは以下の様に赤字以外の部分は、変更していません。その為、後半部分は変更がないので省略しました。

using System;
using System.Collections.Specialized;
using System.IO;
using System.Windows.Forms;
using ICSharpCode.SharpZipLib.Zip;
using ICSharpCode.SharpZipLib.Core;

namespace Acha_ya.SampleClass
{
    [Serializable]
    public class SharpZipLibWrapper : MarshalByRefObject, Acha_ya.InterfaceClass.ISharpZipLibWrapper
    {
        private string zipFileName;
        private bool isContinue;  // 新規作成中/解凍処理中の処理継続フラグ
        private frmProgress formProgress = null;
        public SharpZipLibWrapper() : this(null)
        {
        }
        public SharpZipLibWrapper(string zipFileName)
        {
            ZipFileName = zipFileName;
        }

        //public bool Enabled
        //{
        //    get
        //    {
        //        // SharpZipLib が動作するか(リンクされているか)確認する為にプロパティに
        //        // アクセスする。これにより、リンクされていない場合は、アプリケーション側で
        //        // 例外が発生する。
        //        int codePage = ZipConstants.DefaultCodePage;
        //        return true;
        //    }
        //}

        public string ZipFileName
        {
            get { return zipFileName; }
            set { zipFileName = value; }
        }

        /// <summary>
        /// ZIP書庫一覧を取得して、ListBoxに登録します。
        /// </summary>
        /// <param name="fileList">登録する StringCollection</param>
        public void FileList(out StringCollection fileList)
        {
            fileList = new StringCollection();
            ZipFile zipFile = new ZipFile(ZipFileName);
            foreach (ZipEntry ze in zipFile)
            {
                fileList.Add(ze.Name);
            }
            zipFile.Close();
        }

        (以降略)
}

注目点は、FileList メソッドの引数を、ListBox から StringCollection に変更している点です。これは、SerializableAttribute 属性を持たせた為に、ListBox を引数のままにしていると、FileList メソッド呼び出し時に SerializationException が発生してしまいます。その為の変更です。
それと、Enabled プロパティをなくしていますが、こちらの方法では必要ないので削除しています。

3.アプリケーション側からの呼び出し

ではアプリケーション側からの呼び出しです。まず、前回までの Assembly.LoadFile による方法では、開放が出来ない事もあって、フォームの Load イベントで 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;
        // 実質的なAddMember2処理部の始まり
        zip.ZipFileName = archiveFile;
        zip.AddMember(addFiles);
        // 実質的なAddMember2処理部の終わり
    }
    catch (FileNotFoundException e)
    {
        MessageBox.Show("動的リンクに失敗しました", e.FileName,
            MessageBoxButtons.OK, MessageBoxIcon.Stop);
    }
    finally
    {
        if (appDomain != null)
            AppDomain.Unload(appDomain);
    }
}

大半は、リンクと開放に関連した処理で、実質的な処理部分は、2行しかありません。また、特徴として、メソッドを静的リンクと同じ様な記述で呼び出せる点があります。以前の動的リンクの方法だと、InvokeMember によりちょっと見難い記述になっていた部分がすっきりしています。

では、書庫メンバ一覧取得部分を見てみましょう。

/// <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;
        appDomain = AppDomain.CreateDomain("ZipManagedLink");
        handle = appDomain.CreateInstance("SharpZipLibWrapper",
            "Acha_ya.SampleClass.SharpZipLibWrapper");
        zip = handle.Unwrap() as ISharpZipLibWrapper;
        // 実質的なFileList2処理部の始まり
        zip.ZipFileName = archiveFile;
        zip.FileList(out nameList);
        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);
    }
}

こちらは、引数が ListBox ではなく、StringCollection に変更されたのを受けて、少し記述が増えていますが、それ以外は書庫メンバ追加同様の記述であり、こちらも特に問題はありません。但しこの影響で、ListBox の各 Item への登録内容が、ZipEntry オブジェクトではなく、メンバ名の文字列だけになっています。その点では、前回の方法によるサンプルと等価とは言えないかもしれません。

他の機能(書庫のメンバ削除/一括解凍/新規書庫作成)については、ほぼ書庫へのメンバ追加と同じ様な記述となるので、記事としては割愛します。このあたりを見比べたい方は、本記事最後のサンプルソースをダウンロードして下さい。

4.サブドメインへのリンクによる実用度

この方法では、MarshalByRefObject を継承する必要性がある事から、SharpZipLib を直接動的リンクする事は出来ません。従って必然的にラッパークラスを用意する必要が出て来ます。また、ソースは簡単に作成出来るとはいえ、インターフェース用の DLL を別途用意する必要も出て来て、プログラム構成的には複雑になる欠点はあります。
更に、メンバ一覧の引数を ListBox から、StringCollection に変更したように、シリアライズ属性を持たせる為の制約も出て来ます。

但し、インターフェースを別途定義する事により、メソッドの呼び出し等はごく普通に行なえる点が魅力となります。動的リンクにおいて、検討する価値はありそうです。

5.インターフェースの定義は必須か?

さて、冒頭に示した元ネタに従い、ここまでやってみましたが、一つ疑問が生じました。インターフェース定義は必要でしょうか。
確かにこれを定義する事により、動的リンクするDLLのメソッド呼び出しは楽になります。しかし、今回の場合はラッパークラス定義自体が必要な為、前回のハイブリッドタイプの方法と比べると、呼び出し側の面倒さにそれ程の差は出ません。であれば、アプリケーション構成をシンプルにする為にインターフェース定義を省略する手もありそうです。

また視点を変えれば、前回のハイブリッドタイプに今回の様なインターフェース定義をした方法も出来るのではないでしょうか。せっかくのC#研究なので、ここまでやってみたいと思います。よって、次回は、このあたりの話をします。

では、最後に今回の記事で使用したソース一式を、CAB形式にまとめてアップロードしておきます。

「DLLLink3.cab」をダウンロード

« マネージドDLLの動的リンク(2) | トップページ | マネージドDLLの動的リンク(4)[最終回] »

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

コメント

コメントを書く

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

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

トラックバック


この記事へのトラックバック一覧です: マネージドDLLの動的リンク(3):

« マネージドDLLの動的リンク(2) | トップページ | マネージドDLLの動的リンク(4)[最終回] »