マネージ実行プロセス

マネージド実行プロセスには以下の手順が含まれます。詳細については、このトピックで後ほど説明します。

  1. コンパイラを選択します。 共通言語ランタイムが提供する機能を利用するためには、共通言語ランタイムに対応した言語コンパイラを使用する必要があります。
  2. コードを中間言語にコンパイルします。 コンパイルを実行すると、ソース コードが共通中間言語 (CIL) に変換され、必要なメタデータが生成されます。
  3. CIL をネイティブ コードにコンパイルします。 実行時に、Just-In-Time (JIT) コンパイラによって CIL がネイティブ コードに変換されます。 コードは、このコンパイル中に、CIL とメタデータを検査してコードがタイプ セーフであると判断できるかどうかを確認する検証プロセスに合格する必要があります。
  4. コードを実行します。 共通言語ランタイムは、実行を可能にするインフラストラクチャと実行時に使用できるサービスを提供します。

コンパイラを選択する

共通言語ランタイム (CLR: Common Language Runtime) によって提供される機能を活用するには、Visual Basic、C#、Visual C++、F# などのランタイムに対応した言語コンパイラか、Eiffel、Perl、COBOL などのサードパーティのコンパイラを使用する必要があります。

共通言語ランタイムは多言語実行環境であるため、さまざまなデータ型と言語機能をサポートしています。 使用する言語コンパイラによって、利用できる共通言語ランタイムの機能が決まり、その機能を使用してコードをデザインすることになります。 記述するコードの構文を決定するのは、共通言語ランタイムではなく、使用するコンパイラです。 作成したコンポーネントを他の言語で記述されたコンポーネントでも完全に使用できるようにするためには、そのコンポーネントからエクスポートされた型が、Common Language Specification (CLS) に規定されている言語機能だけを公開するようにする必要があります。 CLSCompliantAttribute 属性を使用することにより、コードを確実に CLS に準拠させることができます。 詳細については、「言語への非依存性、および言語非依存コンポーネント」を参照してください。

CIL にコンパイルする

マネージド コードにコンパイルする場合、コンパイラはソース コードを共通中間言語 (CIL) に変換します。これは、効率的にネイティブ コードに変換できる、CPU に依存しない命令セットです。 CIL には、オブジェクトの読み込み、格納、初期化、メソッドの呼び出しを行うための命令に加え、算術演算と論理演算、制御フロー、直接メモリ アクセス、例外処理、その他の操作を行うための命令が含まれています。 コードは実行の前に、CIL を CPU 固有のコードに変換する必要があり、通常、Just-In-Time (JIT) コンパイラにより行われます。 共通言語ランタイムは、サポートするコンピューター アーキテクチャごとに JIT コンパイラを 1 つ以上提供しているため、同じ CIL セットを JIT コンパイルして、サポートされているすべてのアーキテクチャ上で実行できます。

コンパイラは CIL を生成するときに、メタデータも生成します。 メタデータには、コード内の型について、それぞれの型の定義、型のメンバーのシグネチャ、コードが参照するメンバー、共通言語ランタイムが実行時に使用するその他のデータなどが記述されています。 CIL とメタデータは、実行可能ファイルのコンテンツとして使われてきた従来の Microsoft PE と Common Object File Format (COFF) に基づいて拡張したポータブル実行可能 (PE) ファイルに含まれています。 このファイル形式は CIL またはネイティブ コードとメタデータに対応しており、オペレーティング システムが共通言語ランタイムのイメージを認識できるようになります。 CIL と共にファイル内にメタデータが存在することにより、コード自体が自己記述となることができます。つまり、タイプ ライブラリやインターフェイス定義言語 (IDL) は必要ありません。 共通言語ランタイムは、実行時にこのファイルから必要に応じてメタデータを検出および抽出します。

CIL をネイティブ コードにコンパイルする

共通中間言語 (CIL) を実行する前に、CIL を共通言語ランタイムに対してコンパイルして、対象マシン アーキテクチャのネイティブ コードにする必要があります。 .NET には、この変換を実行する 2 つの方法が用意されています。

JIT コンパイラによるコンパイル

JIT コンパイルは、アプリケーションの実行時、つまりアセンブリの内容が読み込まれて実行されるときに、オンデマンドで CIL をネイティブ コードに変換します。 共通言語ランタイムには、サポートされている CPU アーキテクチャごとに JIT コンパイラが用意されているため、開発者は、JIT コンパイルが可能な CIL アセンブリのセットを構築し、異なるマシン アーキテクチャを持つさまざまなコンピューター上で実行できます。 ただし、マネージド コードがプラットフォーム固有のネイティブ API またはプラットフォーム固有のクラス ライブラリを呼び出す場合、そのコードはそのオペレーティング システムでしか実行されません。

JIT コンパイルは、実行時に呼び出されることがないコードがある可能性を考慮しています。 PE ファイル内のすべての CIL を時間とメモリを使ってネイティブ コードに変換するのではなく、実行中に必要に応じて CIL を変換し、結果のネイティブ コードをメモリに保存して、そのプロセスのコンテキスト内で後続の呼び出しがアクセスできるようにします。 型が読み込まれて初期化されるとき、ローダーはスタブを作成し、その型の各メソッドにそれを結び付けます。 メソッドが初めて呼び出されるとき、スタブは JIT コンパイラに制御を渡します。JIT コンパイラは、そのメソッドの CIL をネイティブ コードに変換し、生成されたネイティブ コードを直接指すようにスタブを変更します。 このため、JIT でコンパイルされたメソッドに対する後続の呼び出しでは、ネイティブ コードが直接実行されます。

NGen.exe を使ったインストール時のコード生成

JIT コンパイラは、アセンブリで定義されている個々のメソッドが呼び出されるときにアセンブリの CIL をネイティブ コードに変換するため、実行時のパフォーマンスに悪影響を及ぼします。 ほとんどの場合、このパフォーマンスの低下は許容範囲内です。 より重要な点として、JIT コンパイラが生成したコードは、コンパイルを起動したプロセスにバインドされます。 複数のプロセスの間でこれを共有することはできません。 生成されたコードを、1 つのアプリケーションの複数の呼び出しの間で、または 1 つのアセンブリ セットを共有する複数のプロセスの間で共有できるようにするために、共通言語ランタイムは事前コンパイル モードをサポートします。 この事前コンパイル モードでは、Ngen.exe (ネイティブ イメージ ジェネレーター) を使って、JIT コンパイラと同様に CIL アセンブリをネイティブ コードに変換します。 ただし、Ngen.exe の動作は、以下の 3 つの点で JIT コンパイラの動作と異なります。

  • CIL からネイティブ コードへの変換は、アプリケーションの実行中ではなく、アプリケーションの実行前に行われます。
  • メソッドを 1 つずつコンパイルするのではなく、アセンブリ全体を一度にコンパイルします。
  • 生成したコードを、ディスク上のファイルとしてネイティブ イメージ キャッシュに保持します。

コードの検証

管理者がコードの検証を省略できるセキュリティ ポリシーを確立していない限り、CIL コードはネイティブ コードへのコンパイルの一環として検証プロセスに合格する必要があります。 検証では、CIL とメタデータを調べて、コードがタイプ セーフであるかどうかを確認します。これは、コードがアクセスを許可されているメモリ位置にのみアクセスすることを意味します。 タイプ セーフは、オブジェクトを相互に分離するため、不注意や悪意による破損からオブジェクトを保護するために役立ちます。 また、コードに対するセキュリティ制限が強制適用されることも保証されます。

共通言語ランタイムは、検証可能なタイプ セーフ コードが次の条件を満たすことを前提としています。

  • 型への参照には参照される型との間に完全な互換性がある。
  • 適切に定義された操作だけがオブジェクトに対して呼び出される。
  • ID が正しい。

検証プロセスでは、CIL コードが検査され、コードが適切に定義された型を介してのみメモリ位置にアクセスし、メソッドを呼び出すことができるかどうかが確認されます。 たとえば、オブジェクトのフィールドにアクセスするときにメモリ位置をオーバーランできるようなコードは許可されません。 さらに、不正な CIL はタイプ セーフ規則の違反につながる可能性があるため、検証ではコードを検査して CIL が正しく生成されているかどうかを判断します。 検証プロセスは適切に定義されたタイプ セーフ コードのセットを許可します。許可されるコードはタイプ セーフなコードだけです。 ただし、タイプ セーフなコードの中にも、検証プロセスの制約事項のために検証を通過しないコードがあります。また、言語によっては、デザイン上、検証可能なタイプ セーフ コードが生成されない場合もあります。 セキュリティ ポリシーがタイプ セーフなコードを必要とするにもかかわらず、コードが検証を通過しない場合には、そのコードの実行時に例外がスローされます。

コードの実行

共通言語ランタイムは、マネージド実行を可能にするインフラストラクチャと実行時に使用できるサービスを提供します。 メソッドは、実行する前にプロセッサ固有のコードにコンパイルされている必要があります。 CIL が生成されている各メソッドは、初めて呼び出されたときに JIT コンパイルされてから実行されます。 次にこのメソッドが実行されるときには、JIT コンパイル済みの既存のネイティブ コードが実行されます。 JIT コンパイルからコード実行までのプロセスは、実行が完了するまで繰り返されます。

実行時に、マネージド コードは、ガベージ コレクション、セキュリティ、アンマネージド コードとの相互運用性、言語間デバッグ サポート、強化された配置とバージョン管理のサポートなどのさまざまなサービスを利用できます。

Microsoft Windows Vista では、オペレーティング システム ローダーによって、COFF ヘッダー内のビットを調べることでマネージド モジュールがチェックされます。 設定されたビットはマネージド モジュールを意味します。 ローダーがマネージド モジュールを検出すると、mscoree.dll が読み込まれます。マネージド モジュール イメージが読み込まれるときとアンロードされるときには、 _CorValidateImage および _CorImageUnloading がローダーに通知します。 _CorValidateImage は、次のアクションを実行します。

  1. コードが有効なマネージド コードであることを確認します。
  2. イメージのエントリ ポイントをランタイムのエントリ ポイントに変更します。

64 ビット Windows では、 _CorValidateImage は、メモリ内のイメージを PE32 から PE32+ 形式に変換することによって変更します。

関連項目