« マネージドDLLの動的リンク(1) | トップページ | マネージドDLLの動的リンク(3) »

2010年11月12日 (金)

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

マネージドDLLの動的リンク(1)の続きです。
前回、直接 SharpZipLib を動的リンクして使用するのは、使い方にもよるものの、非現実的だと結論づけました。今回は、SharpZipLib のラッパークラスを作成し、ラッパークラスを動的リンクする事について考えてみます。

まず、全体の構成を図示すると右図の様になります。

アプリケーション側では、SharpZipLib ラッパークラスのみを呼び出して利用します。そして、ここを動的リンクします。ラッパークラスからは、細かい SharpZipLib の制御を行なう事により、アプリケーション側がラッパークラスを単純な呼び出しのみでまかなえる様になっています。ラッパークラスと SharpZipLib は静的リンクさせる為、ラッパークラスのプログラムは、通常の方法で行なえます(生産性や保守性は失われない)。この様な一種のハイブリッド構成にする事で、静的リンクによるプログラムの作り易さと、動的リンクによるメリットを活かせるかを検討します。

1.ラッパークラス

ラッパークラスのプログラムですが、以下の様に作成しました。実際は、一括解凍メソッドもいれてありますが、下記リストでは省略しています。基本的には、前回の静的リンクで使用していた内容と同一です。但し、新規作成時は進捗状況をフォームで表示する様にして、ちょっと本格的な感じにしてみました。
また特記事項として、Enabled プロパティを追加しています。これは、SharpZipLib 側の DLL が本当にリンクされているかをテストする為に用意しています。実際にはプロパティの値により判別するのではなく、プロパティを呼び出したときに、例外が発生するかどうかで判別します。もうちょっとスマートな方法があればいいのですが、例外がこのラッパークラスで発生するのでなく、呼び出したアプリケーション側で発生します。この為、Enabled プロパティの get アクセサ中で、try ~ catch しても例外を拾えないので、このままにしています。

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

namespace Acha_ya.SampleClass
{
    public class SharpZipLibWrapper
    {
        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="listBox">登録する ListBox</param>
        public void FileList(ListBox listBox)
        {
            listBox.Items.Clear();
            ZipFile zipFile = new ZipFile(ZipFileName);
            foreach (ZipEntry ze in zipFile)
            {
                listBox.Items.Add(ze);
            }
            zipFile.Close();
        }

        /// <summary>
        /// ZIP書庫への指定した複数のファイルメンバを追加します。
        /// </summary>
        /// <param name="addFiles">追加対象となるファイルパスの配列</param>
        public void AddMember(string[] addFiles)
        {
            ZipFile zipFile = new ZipFile(ZipFileName);
            zipFile.BeginUpdate();
            try
            {
                foreach (string file in addFiles)
                {
                    string member = Path.GetFileName(file); // ファイル名をエントリ名にする
                    // メンバを追加する
                    // useUnicodeText は、ファイル名の Shift-JIS 表現が可能な限り false 指定の方が互換性がよい
                    zipFile.Add(file, ICSharpCode.SharpZipLib.Zip.CompressionMethod.Deflated, false);
                }
                zipFile.CommitUpdate();
            }
            catch (Exception e)
            {
                zipFile.AbortUpdate();
                MessageBox.Show(e.Message, e.Source, MessageBoxButtons.OK, MessageBoxIcon.Stop);
            }
            zipFile.Close();
        }

        /// <summary>
        /// ZIP書庫への指定したファイルメンバを削除します。
        /// </summary>
        /// <param name="member">削除するメンバ名</param>
        public void DeleteMember(string member)
        {
            ZipFile zipFile = new ZipFile(ZipFileName);
            bool result = false;
            zipFile.BeginUpdate();
            try
            {
                result = zipFile.Delete(member);
                if (!result)
                    MessageBox.Show(member + "の削除に失敗しました");
                zipFile.CommitUpdate();
            }
            catch (Exception e)
            {
                zipFile.AbortUpdate();
                MessageBox.Show(e.Message, e.Source, MessageBoxButtons.OK, MessageBoxIcon.Stop);
            }
            zipFile.Close();
        }
        /// <summary>
        /// 静的リンクによるフォルダ単位の新規書庫作成
        /// </summary>
        /// <param name="archiveFile">作成するZIP書庫のフルパス</param>
        /// <param name="compFolder">書庫作成元フォルダパス</param>
        public void CreateZip(string archiveFile, string compFolder)
        {
            FastZipEvents fastZipEvents = new FastZipEvents();
            fastZipEvents.Progress = new ProgressHandler(CreateProgress);
            fastZipEvents.ProcessFile = new ProcessFileHandler(ZipProcessFile);
            FastZip fastZip = new FastZip(fastZipEvents);
            fastZip.CreateEmptyDirectories = true;
            fastZip.UseZip64 = UseZip64.Dynamic;
            isContinue = true;
            try
            {
                formProgress = new frmProgress();
                formProgress.CancelClick += new EventHandler(formProgress_CancelClick);
                formProgress.Show();
                fastZip.CreateZip(archiveFile, compFolder, true, null);
                ZipFileName = archiveFile;
            }
            finally
            {
                if (formProgress != null)
                    formProgress.Dispose();
                formProgress = null;
            }
        }

        // 進捗状況ダイアログでキャンセルボタンがクリックされた時のイベント処理
        private void formProgress_CancelClick(object sender, EventArgs e)
        {
            isContinue = false;
        }
        // CreateZipメソッド内から Progress イベント処理として呼び出される。
        private void CreateProgress(Object sender, ProgressEventArgs e)
        {
            formProgress.lblFolder.Text = Path.GetDirectoryName(e.Name);
            formProgress.lblFile.Text = Path.GetFileName(e.Name);
            formProgress.pgbFile.Value = (int)e.PercentComplete;
            Application.DoEvents();
            // ここで false をセットしてもキャンセルされない模様(仕様?)
            // 実質的に、ProcessFile イベントにてキャンセル処理している。
            e.ContinueRunning = isContinue;
        }
        // CreateZipメソッド内から ProcessFile イベント処理として呼び出される。
        private void ZipProcessFile(Object sender, ScanEventArgs e)
        {
            e.ContinueRunning = isContinue;
        }
    }
}

このラッパークラスを使用すれば、一つのメソッド呼び出しのみで書庫の新規作成・書庫一覧取得・メンバ追加・削除(・一括解凍)ができます。

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

呼び出し側のアプリケーション側のプログラムですが、こちらはラッパークラスの DLL を動的リンクにする為、まずフォームの Load イベントを以下の様に記述しました。

private void Form1_Load(object sender, EventArgs e)
{
    try
    {
        string dllPath = Path.Combine(Path.GetDirectoryName(Application.ExecutablePath),
            "SharpZipLibWrapper.dll");
        zip = Assembly.LoadFile(dllPath);
        Type sharpZipLibWrapper = zip.GetType("Acha_ya.SampleClass.SharpZipLibWrapper",
            true, false);
        object zf = sharpZipLibWrapper.InvokeMember(null, BindingFlags.CreateInstance,
            null, null, null);
        bool enabled = (bool)sharpZipLibWrapper.InvokeMember("Enabled",
            BindingFlags.GetProperty, null, zf, null);
    }
    catch (Exception ex)
    {
        if (ex.InnerException == null)
            MessageBox.Show(ex.Message, ex.Source, MessageBoxButtons.OK, MessageBoxIcon.Stop);
        else
            MessageBox.Show(ex.InnerException.Message, ex.InnerException.Source,
                MessageBoxButtons.OK, MessageBoxIcon.Stop);
        btnList.Enabled = false;
        btnCreate.Enabled = false;
    }
}

ここでは、自前で用意する SharpZipLibWrapper.dll は存在する筈ですが、SharpZipLib(ICSharpCode.SharpZipLib.dll) は存在しないかもしれない前提としています。そこで、存在チェックの為に Enabled プロパティを取得しようとしています。ここで、SharpZipLib が存在すれば、そのまま正常処理される訳ですが、存在しなかった場合は例外が発生するので、それに合わせてボタンを無効化して、SharpZipLib の機能を使用出来ない様にアプリケーションを立ち上げています。

次に書庫の各機能の処理部分です。どれも同じ様な感じになりますので、一括して載せます。

/// <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 });
}

/// <summary>
/// ラッパークラスの動的リンクによるZIP書庫へのメンバ追加
/// </summary>
/// <param name="archiveFile">ZIP書庫ファイルのフルパス</param>
/// <param name="addFiles">追加対象となるファイルパスの配列</param>
private void AddMember3(string archiveFile, string[] addFiles)
{
    Type sharpZipLibWrapper = zip.GetType("Acha_ya.SampleClass.SharpZipLibWrapper", true, false);
    object zf = sharpZipLibWrapper.InvokeMember(null, BindingFlags.CreateInstance, null, null,
        new object[] { archiveFile });
    sharpZipLibWrapper.InvokeMember("AddMember", BindingFlags.InvokeMethod, null, zf,
        new object[] { addFiles });
}

/// <summary>
/// ラッパークラスの動的リンクによる書庫メンバ削除
/// </summary>
/// <param name="archiveFile">ZIP書庫ファイルのフルパス</param>
/// <param name="member">削除するメンバ名</param>
private void DeleteMember3(string archiveFile, string member)
{
    Type sharpZipLibWrapper = zip.GetType("Acha_ya.SampleClass.SharpZipLibWrapper", true, false);
    object zf = sharpZipLibWrapper.InvokeMember(null, BindingFlags.CreateInstance, null, null,
        new object[] { archiveFile });
    sharpZipLibWrapper.InvokeMember("DeleteMember", BindingFlags.InvokeMethod, null, zf,
        new object[] { member });
}

/// <summary>
/// ラッパークラスの動的リンクによるフォルダ単位の新規書庫作成
/// </summary>
/// <param name="archiveFile">作成するZIP書庫のフルパス</param>
/// <param name="compFolder">書庫作成元フォルダパス</param>
private void CreateZip3(string archiveFile, string compFolder)
{
    Type sharpZipLibWrapper = zip.GetType("Acha_ya.SampleClass.SharpZipLibWrapper", true, false);
    object zf = sharpZipLibWrapper.InvokeMember(null, BindingFlags.CreateInstance, null, null,
        new object[] { archiveFile });
    sharpZipLibWrapper.InvokeMember("CreateZip", BindingFlags.InvokeMethod, null, zf,
        new object[] { archiveFile, compFolder });
}

とりあえず、前回のサンプルと比較出来るように、型取得・インスタンス生成・メソッド実行を行なっていますが、最初の2つ(型取得・インスタンス生成)は、どれも全く同じ事をやっている訳で、無駄ですね。最後にダウンロード出来るサンプルコードでは、このあたりは最適化しています。

3.ハイブリッドタイプのリンク方法による実用度

このように、ハイブリッドタイプにすると動的リンクに伴う、面倒な動的メソッド呼び出し等が最小限に抑えられ、かつ動的リンクとしての機能も果たす事が出来ました。

実際、ラッパークラスは静的リンクのテスト用サンプルからの流用で、あっという間に作成出来ました。おかげで、進捗状況フォーム表示にまでやってしまった位です。また、呼び出し側となるアプリケーション側の実装も、苦にならないレベルでした。

もちろん、ラッパークラスが別アセンブリになるので、実行環境では1つ余計なファイルが必要な構成になってしまいますが、実用性を考えれば許容できるのではないでしょうか。
ただし、最初の方に述べた Enabled プロパティアクセスによる、リンクが出来ているかのテストがスマートでないのがちょっと難点ですかね。ここはもっといい方法があれば教えて欲しいです。

以上の点を考えれば、マネージドDLL を動的リンクしたい場合は、このようなハイブリッドタイプの検討をした方がいいでしょう。

さて、これで結論が出ましたが、この記事は最終回でなく、まだ続きます。というのも、別の方法による動的リンクが出来るからです。次回は、この別な方法による動的リンクによる実装研究を行ないたいと思います。

最後に今回の記事で使用したソース一式を、CAB形式にまとめてアップロードしておきます。これには、記事では省略した一括解凍分も含まれています。

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

« マネージドDLLの動的リンク(1) | トップページ | マネージドDLLの動的リンク(3) »

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

コメント

コメントを書く

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

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

トラックバック


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

« マネージドDLLの動的リンク(1) | トップページ | マネージドDLLの動的リンク(3) »