第4回 ClickOnceテクノロジを最大限に生かす開発連載 ClickOnceの真実(3/4 ページ)

» 2006年09月02日 00時00分 公開
[一色政彦デジタルアドバンテージ]

●ダウンロードした.DLLファイルのロードと、そこに含まれる機能の実行

 ダウンロードした.DLLファイルに含まれる機能(例えばクラスなど)を利用することは、まったく難しくない。というのも、その機能を通常と同じようにそのまま呼び出せばよいだけだからだ。このようなことが可能なのは、「.NET FrameworkのCLRでは、あるクラスを利用しようとしているメソッドが呼び出されるときに、初めてJITコンパイラがそのクラスのアセンブリをロードしようとする」という仕組みのおかげである。

 ただしアセンブリをロードするときに、そのファイルが見つからない場合、FileNotFoundException例外(System.IO名前空間)が発生してしまう。この問題を避けるために、JITコンパイラが動くのを、ファイル(アセンブリ)がダウンロードされた後まで遅延させなければならない。これを行うには、アセンブリの機能の呼び出し(つまりクラスのインスタンス生成や静的メソッドの使用など)を、ほかのメソッドの中にカプセル化し(つまり1つのメソッドの中にまとめて)、さらにそのメソッドの呼び出しがインライン展開されないようにしておいた方がよい。

 インライン展開されないようにするには、MethodImplOptions列挙体のNoInlining値をパラメータに指定したMethodImpl属性(=MethodImplAttributeクラス)を、そのメソッドに付加すればよい(この列挙体と属性はSystem.Runtime.CompilerServices名前空間に所属する)。

 要するに、次のようなコードを書けばよいわけである。なおこのコードは、[オンデマンド配置]ボタンがクリックされたときのイベント・ハンドラである。

private void buttonOnDemand_Click(object sender, EventArgs e)
{
  if (MyDownloadFileGroup("SampleClassLibraryGroup"))
  {
    // オンデマンド配置されるアセンブリの
    // 機能の呼び出しをメソッドの中にカプセル化
    UseSampleClassLibrary();
  }
  else
  {
    MessageBox.Show(
      "SampleClassLibraryがダウンロードされていないため、\n" +
      "この機能は現在利用できません。");
  }
}

// インライン展開されないように属性を付加
[MethodImpl(MethodImplOptions.NoInlining)]
private void UseSampleClassLibrary()
{
  // ダウンロードしたアセンブリに含まれるコードを実行
  DownloadedDialog dlg = new DownloadedDialog();
  dlg.ShowDialog();
}

ダウンロードされたアセンブリ(.DLLファイル)に含まれるクラスを呼び出すコード例
ダウンロードされた後でアセンブリが正しくCLR上にロードされるように、そのアセンブリに含まれるクラスを使用するコードはメソッド内にまとめて記述し、さらにそのメソッドには「MethodImplOptions.NoInlining」をパラメータに指定したMethodImpl属性を付加する。

 上記のコードでは、オンデマンド配置されるアセンブリの機能をUseSampleClassLibraryメソッドの中にカプセル化している。さらに、そのUseSampleClassLibraryメソッドにはインライン展開されないように属性を付加している。

●ロード失敗時のアセンブリの再ロード

 通常は上記のコードで問題ないはずだが、何らかの理由でダウンロードしたアセンブリのロードに失敗した場合、その失敗の情報をCLRがキャッシュしてしまうらしく、アプリケーションを再起動しないと、ダウンロードしたアセンブリ(.DLLファイル)のクラスにアクセスできずにエラーが発生する。

 これを回避するために、CLRがアセンブリのロードに失敗したときに発行されるAssemblyResolveイベントをハンドルして、そこで独自にアセンブリをロードする(Assembly.LoadFileメソッド)ような仕組みを用意しておいた方が安全だ。

 AssemblyResolveイベントはアプリケーション・ドメインで発生するイベントであり、現在のアプリケーション・ドメインは、AppDomainクラス(System名前空間)のCurrentDomainプロパティより取得できる。

 本稿で提供する「ClickOnce」プロジェクトのクラス・ライブラリ内でClickOnceAssemblyクラス(DigitaAdvantage.ClickOnce名前空間)では、このAssemblyResolveイベントの処理も実装している。ここでは、このクラスを活用して先ほど示したコードを拡張したものを示しておこう。

private void buttonOnDemand_Click(object sender, EventArgs e)
{
  if (MyDownloadFileGroup("SampleClassLibraryGroup"))
  {
    if (LoadSampleClassLibrary())
    {
      UseSampleClassLibrary();
    }
    else
    {
      MessageBox.Show(
        "この機能は現在利用できません。\n" +
        "SampleClassLibraryがダウンロードされていない可能性が" +
        "あります。");
    }
  }
  else
  {
    ……中略……
  }
}

// オンデマンドで取得したアセンブリ(.dllファイル)をロードする
private bool LoadSampleClassLibrary()
{
  ClickOnceAssembly asm = new ClickOnceAssembly();
  return asm.LoadDeployedFile(
    Application.StartupPath,
    "SampleClassLibrary",
    new CheckLibraryClassDelegate(asm_CheckLibraryClassDelegate));
}

// アセンブリをロードするためのデリゲート・メソッド
[MethodImpl(MethodImplOptions.NoInlining)]
void asm_CheckLibraryClassDelegate()
{
  SampleClassLibraryCheckClass dummy =
    new SampleClassLibraryCheckClass();
}

AssemblyResolveイベントを活用して動的にアセンブリをロードするコード

 このコードでは、先ほどのUseSampleClassLibraryメソッドを呼び出す前に、LoadSampleClassLibraryメソッドを呼び出している。

 このLoadSampleClassLibraryメソッドは、オンデマンド配置されたアセンブリ(のクラス)が正しくロードできるかどうかを検証するためのものだ。正しくロードできた場合にはTrue、そうでない場合はFalseを戻り値として返す。この戻り値がTrueのときにだけ、UseSampleClassLibraryメソッドを呼び出すというロジックになっている。

 肝心のLoadSampleClassLibraryメソッドの内部では、ClickOnceAssemblyクラスをインスタンス化して、そのLoadDeployedFileメソッドを呼び出している。このLoadDeployedFileメソッドは、アセンブリを安全にロードしてくれ(厳密には先ほど説明したAssemblyResolveイベントを利用してアセンブリをロードする)、正常にロードできた場合はTrueを、何らかのエラーが発生した場合はFalseを戻り値として返す。

 LoadDeployedFileメソッドの第1パラメータには.DLLファイルが格納されているパスを指定し(通常は.EXEファイルと同じ場所にダウンロードされるため、「Application.StartupPath」を指定すればよい)、第2パラメータに.DLLファイルの名前を指定する(「.dll」という拡張子部分は除く)*1。第3パラメータには、CheckLibraryClassDelegateデリゲート(DigitaAdvantage.ClickOnce名前空間)を指定する。このデリゲートの構文はパラメータなしで、戻り値はvoid型(=なし)である。

*1 ここで.DLLファイルのパスと名前を取得しているのは、AssemblyResolveイベントのハンドラでAssembly.LoadFileメソッドを呼び出す際にそれらの値を使用するからである。


 デリゲート経由で呼ばれるメソッドは、アセンブリ(.DLLファイル)をロードするために使われる。従ってそのデリゲート・メソッド(本稿ではasm_CheckLibraryClassDelegateメソッド)内では、アセンブリに含まれるクラスをインスタンス化するなどすればよい(本稿の例ではSampleClassLibraryCheckClassという空のクラスをインスタンス化している)*2

 asm_CheckLibraryClassDelegateメソッドが呼び出される際、CLRのJITコンパイラが働き、アセンブリをロードしようとする。万が一、そのロードに失敗すると、アプリケーション・ドメインでAssemblyResolveイベントが発生する。そのイベントをClickOnceAssemblyクラスがハンドルして、LoadDeployedFileメソッドのパラメータに指定されたパスの.DLLファイルをアセンブリとしてロードする。このような仕組みによって、アセンブリ内のクラスが利用可能となるわけである。

*2 (アセンブリをロードするためだけに)空のクラスのインスタンス化するというのが無駄に感じられる場合は、asm_CheckLibraryClassDelegateメソッドの中に、ダウンロードしたアセンブリに含まれる機能を使用するコードを記述してもよい。例えば、先ほど示したUseSampleClassLibraryメソッドの内容をasm_CheckLibraryClassDelegateメソッドの中に記述してもよい。


 それでは次のテーマに移ろう。

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。