6. アプリケーションの実行までの流れ

更新日: 2009 年 10 月 23 日


ここでは、アプリケーションの起動から実行までに、CLR やアセンブリの内部でどのような処理が行われているのかを追っていきます。

アプリケーションの起動

.NET アプリケーションが起動されるときには、Windows の標準的な仕組みを使用して、まずは .NET Framework の実行エンジンともいえる CLR の動作環境がセットアップされます。

Windows の実行形式のファイルの構造を確認する COFF/PE Dumper (dumpbin.exe) というツールを使用して、.NET アプリケーションが参照しているライブラリを確認して見ましょう。/DEPENDENTS オプションを指定すると、アプリケーションが参照しているライブラリを確認できます。

.NET アプリケーションが起動されるときには、Windows の標準的な仕組みを使用して、まずは .NET Framework の実行エンジンともいえる CLR の動作環境がセットアップされます。

結果を見てみると、mscoree.dll というファイルを参照していることが分かります。この DLL ファイルは、CLR を起動するためのライブラリになります。そして、mscoree.dll は、CLR の本体ともいえる mscorwks.dll を起動します。詳細については、「Side-by-Side 実行」で解説します。

詳細については、「Side-by-Side 実行」で解説します

mscorwks.dll が読み込まれて実行されると、アプリケーションのプロセス空間にアプリケーション ドメインが作成されます。

ページのトップへ


アプリケーション ドメイン (AppDomain)

.NET のアセンブリは、アプリケーション ドメイン (以下、AppDomain) と呼ばれる論理的なプロセス空間で実行されます。.NET アプリケーションを起動すると Windows プロセスが起動されますが、プロセスの内部では 1 つ以上の AppDomain が作成されてアセンブリが実行されることになります。

AppDomain は、Windows のプロセスと同様にそれぞれが独立して実行されますが、プロセスを起動するよりも圧倒的に軽量に動作します。アセンブリのコードの実行は CLR により管理されていますので、複数の AppDomain を 1 つの Windows プロセスで実行しても問題が発生することはありません。

各 Windows プロセス内部では、SystemDomain、SharedDomain と呼ばれる共通的な AppDomain と、開発者が作成したアセンブリが実行される Default AppDomain (既定の AppDomain) が作成されます。また、必要に応じて Default AppDomain だけではなく、複数の AppDomain を作成してプログラムを実行できるようになっています。

また、必要に応じて Default AppDomain だけではなく、複数の AppDomain を作成してプログラムを実行できるようになっています。

CLR が起動されると、最初に SystemDomain が作成されます。SystemDomain は、SharedDomain を作成して、すべての AppDomain で共通的に使用する mscorlib.dll を読み込みます。mscorlib.dll には、Object や String、Enum などの共通的に使用される基本的な型が実装されています。

次に SystemDomain は、Default AppDomain を作成して、起動されたアセンブリを読み込みます。

ページのトップへ


アセンブリのロード

アセンブリが読み込まれる際、アセンブリに厳密名が付けられている場合には、アセンブリの公開キーを使用して署名の妥当性チェックが行われます。これは、アセンブリが開発時と同じものであること、つまり改ざんされていないことを確認します。ただし、GAC に登録されているアセンブリの場合は、GAC の登録時に署名の確認が行われているため、パフォーマンスへの影響を考慮して実行時のチェックはスキップされます。

アセンブリの署名が正しかった場合、そのエビデンスが評価されて、それに応じたアクセス許可セットが付与されます。そして、アセンブリが EXE ファイルである場合には、マニフェスト内の ENTRYPOINT が指す位置から実行が開始されます。

ページのトップへ


JIT コンパイラ

.NET のプログラムは、アセンブリ内部では中間言語である MSIL で記述されています。これをネイティブ コードに変換するのが JIT (Just in Time) コンパイラです。

アセンブリがメモリ上に読み込まれると、メソッド名とメソッドの実体をマッピングする「メソッド テーブル」と呼ばれるテーブル領域が確保されます。アセンブリがメモリ上に読み込まれた時点では、メソッドはすべて JIT コンパイラを呼び出すようになっています。

アセンブリがメモリ上に読み込まれた時点では、メソッドはすべて JIT コンパイラを呼び出すようになっています。

アセンブリが読み込まれて、初めてメソッドが呼び出されると、JIT コンパイラが呼び出されて、MSIL のコード がネイティブ コードにコンパイルされます。そして、コンパイル済みのネイティブ コードは、メモリ上に読み込まれて、メソッド テーブルのポインタがコンパイル済みのネイティブ コードを指すように上書きされます。

そして、コンパイル済みのネイティブ コードは、メモリ上に読み込まれて、メソッド テーブルのポインタがコンパイル済みのネイティブ コードを指すように上書きされます。

このように初回の呼び出しでネイティブ コードへのコンパイルが実行されるため、通常は気になるほどではありませんが、初回時の呼び出しは若干遅くなります。この時間が気になる場合は、事前にアセンブリをネイティブ コードにコンパイルしておく Native Image Generator (NGen.exe) と呼ばれるツールが提供されています。例えば、App.exe をネイティブ コードにコンパイルするには、以下のコマンドを実行します。

Ngen.exe install app.exe

NGen を実行することによって、App.exe とそれが依存しているアセンブリが JIT コンパイラによってネイティブ コードへコンパイルされ、ネイティブ イメージ キャッシュにキャッシュされます。そして App.exe の実行時には、そのキャッシュが利用されます。

ページのトップへ


アセンブリの検索

通常.NET のプログラムは、クラス ライブラリのアセンブリなど、別のアセンブリ (DLL ファイル) で実装されている型を使用し、そのメソッドを呼び出しながら処理を行っていきます。このためプログラムの実行時には、参照しているアセンブリを検索するという作業が最初に必要になります。

.NET 以前の COM では、ファイルの置き場所をレジストリに登録することで、その位置を透過的に管理できていました。しかし、レジストリに情報は登録されているのに、実際のファイルが存在しないなど問題が起こっていました。.NET では、レジストリを使用せず、ファイル システム上でアセンブリ を検索するだけに変更されています。またこれにより、プログラムのインストールはファイルをコピーするだけ、アンインストールはファイルを削除するだけというシンプルなモデルになりました。ここでは、どのようにアセンブリが検索されるのかを具体的に見ていきます。

参照しているアセンブリが厳密名を持つ場合、CLR は以下の順序でアセンブルを探します。厳密名を持たない場合は、3 番目のプローブのみが行われます。

  1. 構成ファイルをチェックして、正しいアセンブリのバージョンを決定します。
  2. グローバル アセンブリ キャッシュ (GAC) を探します。
  3. アセンブリのプローブを行います。

構成ファイルを用いると、異なるバージョンのアセンブリを参照させることができます。CLR はまずこれをチェックします。この詳細については「Side-by-Side 実行」で解説しています。

次に GAC を検索します。GAC に登録されるのは、厳密名が付いたアセンブリのみなので、参照しているアセンブリに厳密名が付いていない場合、GAC の検索はスキップされます。

GAC にも見つからなかった場合、CLR はアセンブリのプローブと呼ばれる操作を行います。プローブでは、次の順序でアセンブリが検索されます。

  1. アプリケーション構成ファイルの <codeBase> 要素を探し、コード ベース内でアセンブリを探します。
  2. 参照しているアセンブリにカルチャが含まれている場合、カルチャ ディレクトリでアセンブリを探します。
  3. 参照しているアセンブリにカルチャが含まれていない場合、アプリケーション ベース ディレクトリでアセンブリを探します。
  4. プライベート binpath でアセンブリを探します。

プローブで最初に使用されるのが、アプリケーション構成ファイルの <codeBase> 要素で指定したコード ベースになります。コード ベースとは CLR がアセンブリを検索する場所であり、その指定はアプリケーション構成ファイルで次のように記述します。

<configuration>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="lib"
                          publicKeyToken="25520ce5d712a2c6"
                          culture="neutral"  />
        <codeBase version="1.0.0.0"
                  href="http://WebServer/lib.dll"/>
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
</configuration>

この例では、コード ベースに URL を指定して、Web サーバーからアセンブリをダウンロードするように構成しています。もちろん、ローカル ディスクを指定することもできます。

参照しているアセンブリにカルチャが含まれている場合 (この場合、サテライト アセンブリと呼ばれる、特定の言語や地域に関するリソースだけのアセンブリとなるのが一般的)、CLR はカルチャ ディレクトリを探します。つまり、次のようなディレクトリでアセンブリが検索されます。ここで、アプリケーション ベースとは、アプリケーションが実行されたディレクトリを指します。

<アプリケーション ベース>\<カルチャ>\<アセンブリ名>.dll

<アプリケーション ベース>\<カルチャ>\<アセンブリ名>\<アセンブリ名>.dll

カルチャが含まれていない場合は、次のディレクトリからアセンブリが検索されます。

[アプリケーション ベース]\[アセンブリ名].dll

[アプリケーション ベース]\[アセンブリ名]\[アセンブリ名].dll

アプリケーション ベースでもアセンブリが見つからない場合、「プライベート binpath」と呼ばれるディレクトリを探します。プライベート binpath を指定するには、アプリケーション構成ファイルで以下のように指定します。

<configuration>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <probing privatePath="lib;lib\v11,lib\v10"
               xmlns="urn:schemas-microsoft-com:asm.v1" />
    </assemblyBinding>
  </runtime>
</configuration>

<probing> 要素の privatePath 属性がプライベート binpathの指定であり、このセミコロン (;) 区切りで記述されたディレクトリを順番に探していき、アセンブリが見つかった場合にはそれを読み込みます。

以上の検索を行ってもアセンブリが見つからなかった場合、CLR は FileNotFoundException 例外を発行して処理を中断します。

ページのトップへ