Microsoft .NET Frameworkとの言語相互運用性の処理

 

ダミアン・ワトキンス
モナッシュ大学

2000 年 10 月

概要:この記事では、Microsoft .NET Frameworkの相互運用性機能について説明します。 (17 ページの印刷)

内容

IntroductionCommon Language Runtime
割り当ての互換性
MetadataCommon 言語仕様 Conclusion

はじめに

分散システムの使用が増えるにつれて、相互運用性はシステム開発者にとって大きな問題となります。 相互運用性の問題は長年にわたって発生しており、これらの問題の一部に対処するために多くの標準とアーキテクチャが開発され、さまざまな程度の成功が得られます。 次に例を示します。

  • 外部データ表現 (XDR) やネットワーク データ表現 (NDR) などの表現標準では、異なるマシン間でデータ型を渡す問題に対処します。 これらの標準は、ビッグ エンディアンやリトル エンディアンの問題、さまざまな単語サイズなどの問題を補います。
  • 分散コンピューティング環境 (DCE) リモート プロシージャ コール (RPC)、オブジェクト管理グループ (OMG) Common Object Request Broker Architecture (CORBA)、Microsoft のコンポーネント オブジェクト モデル (COM) などのアーキテクチャ標準では、言語、プロセス、マシンの境界を越えてメソッドを呼び出す問題に対処します。
  • ANSI C などの言語標準では、コンパイラとマシン間でソース コードを配布できます。
  • SmallTalk や Java の仮想マシン (VM) によって提供される実行環境など、コードをさまざまな物理マシンで実行するには、実行用の標準化された環境を提供します。

これらのアプローチはすべて、アプリケーション開発者に大きな利点をもたらしますが、分散コンピューティング環境に関連するすべての問題を解決したり対処したりした人はありません。 さらに注意が必要な問題の 1 つは、言語の相互運用性の問題です。 言語の相互運用性は、COM や CORBA などの標準化された呼び出しモデルを指すだけでなく、ある言語のクラスとオブジェクトを別の言語の最初のクラス市民として使用できるようにするスキーマです。 理想的には、Python コードで、Eiffel クラスから継承する C++ オブジェクトをインスタンス化できます。 この記事では、このようなアーキテクチャの 1 つである Microsoft .NET Frameworkについて説明します。

共通言語ランタイム

.NET Framework アーキテクチャのコア コンポーネントは、共通言語ランタイムです。 ランタイムは、次の 3 つの主要なコンポーネントで構成されます。

  1. 最新のプログラミング言語で見つかった型と操作の多くをサポートする型システム。
  2. メタデータ システム。これにより、コンパイル時に型を使用してメタデータを永続化し、実行時に実行システムによって尋問することができます。
  3. メモリ管理などのサービスを実行するためにメタデータ システム情報を利用して、.NET Frameworkプログラムを実行する実行システム。

1 つのレベルでは、ランタイムは多くのプログラミング言語の機能の和集合と見なされる場合があります。 標準型を定義し、開発者が独自の型を定義し、型の関数プロトタイプと実装を指定できるようにします。 ランタイムは、分類、継承、ポリモーフィズムなどの機能を備えたオブジェクト指向プログラミング スタイルを容易にサポートします。 ランタイムでは、オブジェクト指向以外の言語をサポートすることもできます。

図 1. 共通言語ランタイムの要素

図 1 は、ランタイムの要素間のリレーションシップの 1 つのビューを示しています。 図の上部にあるソース ファイルには、C++ などのさまざまな言語で新しい型の定義が含まれている場合があります。 この新しい型は、Object などの.NET Framework ライブラリ内の型から継承されます。 このファイルが .NET Framework C++ コンパイラによってコンパイルされると、結果の Microsoft Intermediate Language (MSIL) は、新しい型のメタデータと共にファイルに保持されます。 使用されるメタデータ形式は、型が定義されたプログラミング言語とは無関係です。 この新しい型の MSIL が存在すると、C#、Eiffel、Python、Visual Basic ® などの他の言語で記述された他のソース ファイルをインポートできます。 その後、C++ 型は Python 型であるかのように Python ソース コード ファイル内で使用できます。 型をインポートするプロセスは、異なる言語間で何度も繰り返される場合があります。 これは、ソース ファイルに戻る MSIL の矢印で表されます。 実行時に、実行システムは MSIL ファイルのコンパイル、読み込み、実行を開始します。 別の MSIL ファイルで定義されている型への参照により、そのファイルが読み込まれ、そのメタデータが読み取られ、新しい型のインスタンスがランタイムに公開されます。

CLR 型システム

言語の相互運用性を追求する場合、基本的な問題は残ります。 まず、データ型の表現に関する何らかの形式の合意を採用する必要があります。 CORBA の世界では、オブジェクト管理アーキテクチャはオブジェクトと型の概念を定義し、CORBA 仕様はこれらの概念を定量化します。 .NET Frameworkでは、共通言語ランタイム型システムは、システム内の型を記述します。 その型システムの簡略化された図を図 2 に示します。

図 2. 共通言語ランタイム型システム

共通言語ランタイム型システムは、次の 2 つのサブシステムに分かれています。

  1. 値型
  2. 参照型

値型と参照型の主な違いは、値型に ID の概念がない点です。 値型は、メモリ内の一連のビットです。 値型の 1 つの例は、32 ビット符号付き整数です。 2 つの 32 ビット符号付き整数は、同じ数値を保持している場合と比較されます。 つまり、ビットのシーケンスは同じです。 参照型は、場所、その ID、および一連のビットの組み合わせです。 参照型は、同じクラスの 2 つのオブジェクトに同じデータ値 (同じビット シーケンス) が含まれている可能性がありますが、2 つの異なるオブジェクトを参照しているため、等しいと見なされない可能性がある、従来の C++ クラスに似ています。

.NET Framework用語では、値型または参照型のインスタンスをと呼びます。 すべての値には正確な型が 1 つあり、この型はその値で呼び出すことができるメソッドを定義します。 実行時に、検査によって値の種類を常に決定できるわけではありません。たとえば、値型の正確な型を検査することはできません。

値型

多くの組み込みデータ型は値型ですが、値型は組み込みのデータ型に限定されません。 値の型は、多くの場合、実行時スタックに割り当てられます。したがって、値型はローカル変数、パラメーター、および関数からの戻り値にすることができます。 このような理由から、ユーザー定義の値型は、一般にサイズが 12 バイトから 16 バイト以下の、組み込みの値型と同様に軽量である必要があります。 ユーザー定義の構造体とクラスは、値型であり、次の値を含めることができます。

  • メソッド (クラスとインスタンスの両方)
  • フィールド (クラスとインスタンスの両方)
  • Properties
  • イベント

基本的なレベルでは、プロパティとイベントの両方がメソッドに相当します。 プロパティは構文ショートカットです。これらは、クラスの論理フィールドの Set メソッドと Get メソッドを表します。 イベントは、監視対象オブジェクトで非同期の変更を公開するために使用されます。 クライアントはイベントをリッスンし、イベントが発生すると、クライアントでメソッド呼び出しが呼び出されます。

値型を別の値型から継承することはできません。つまり、.NET Framework用語では、値型がシールされます。 値型はスタックに割り当てられることがよくありますが、値型はヒープに割り当てることができます。 たとえば、オブジェクト型のデータ メンバーの場合です。

すべての値の型には、対応するオブジェクト型 ( ボックス化された型と呼ばれます) が存在します。 任意の値型の値をボックス化およびボックス化解除できます。 値型をボックス化すると、値から、ガベージ コレクションヒープに割り当てられた対応するボックス化された型のオブジェクトにデータがコピーされます。 値型のボックス化を解除すると、ボックス化されたオブジェクトから値にデータがコピーされます。 ボックス型はオブジェクト型であるため、 インターフェイス型 をサポートし、ボックス化されていない表現に追加の機能を提供する場合があります。 オブジェクト型、参照型、およびインターフェイス型については、「参照型」セクションで詳しく説明します。

次のコードは、マネージド C++ での値型の定義と使用を示しています。 値型は、唯一のデータ メンバーとして 2 つの整数を持つクラスです。 既に述べたように、値の型はスタックに割り当てられ、値によって渡されるため、通常は軽量である必要があります。 このコードでは、ヒープに値型を割り当てることができない方法も示しています。ヒープに VTPoint を割り当てようとすると、エラーが発生します。 この例では、キャストを使用して、ヒープから値をボックス化解除します。

#using <mscorlib.dll>

using namespace System;

__value public class VTPoint
{
public:
  int m_x, m_y;
};

int main(void)
{
  VTPoint a;                          // on stack
  VTPoint * ptr = new VTPoint();      // illegal
  __box VTPoint *VTptr = __box(a);              // box
  VTptr->m_x = 42;
  VTPoint b = *dynamic_cast<VTPoint*>(VTptr);   // unbox
  b.m_y = 42;
  Console::Write("Values are: b.m_x should be 42 is ");
  Console::WriteLine(b.m_x);
  Console::Write("Values are: b.m_y should be 42 is ");
  Console::WriteLine(b.m_y);
  return 0;
}

参照型

参照型は、場所 (ID とも呼ばれます) とビットのシーケンスの組み合わせです。 場所は、値を格納できるメモリ内の領域と、そこに格納できる値の種類を指定します。 したがって、場所は、割り当てと互換性のある型のみを場所に格納できる点でタイプ セーフです。 割り当ての互換性の例については、「割り当ての互換性」セクションを参照してください。

前のコードでは、オブジェクト型は常に参照型ですが、すべての参照型がオブジェクト型であるわけではありません。 オブジェクト型の例として、組み込みの参照型 "String" があります。共通言語ランタイムは、オブジェクトという用語を使用して 、オブジェクト 型の値を参照します。 String はオブジェクト型であるため、String 型のすべてのインスタンスはオブジェクトです。 すべてのオブジェクトのすべての正確な型のセットは、オブジェクト型と呼ばれます。 オブジェクトの種類は、常にガベージ コレクションヒープに割り当てられます。

次のコードは、"Point" という名前のオブジェクト型の C++ での定義と使用を示しています。この定義では、プロパティを使用します。これにより、クライアントは、クラスのパブリック フィールドであるかのように論理データ メンバーにアクセスできます。 サンプル コードでは、参照型をスタックに割り当てることができない方法も示しています。スタックに Point を割り当てようとした場合、コンパイルされません。 また、このコードでは、パブリック属性であるかのように、オブジェクトのプロパティに直接アクセスしているように見えるものも示します。 しかし、これは構文的な砂糖です。コンパイラは、必要に応じて、クラスに対して定義されたget_メソッドとset_メソッドの呼び出しを挿入します。

#using <mscorlib.dll>

using namespace System;

__gc class Point
{
  private:
    int m_x, m_y;
  public:
    Point()
    {
      m_x = m_y = 0;
    }  
    __property int get_X()
   {
      return m_x; 
   }
    __property void set_X(int value) 
   { 
      m_x = value;
    };
    __property int get_Y()
   {
      return m_y; 
   }
    __property void set_Y(int value) 
   { 
      m_y = value;
    };
};

int main(void)
{
   Point a;                        // illegal
   Point* ptr = new Point();
   ptr->X = 42;
   ptr->Y = 42;
   Console::Write("ptr->X is ");
   Console::WriteLine(ptr->X);
   Console::Write("ptr->Y is ");
   Console::WriteLine(ptr->Y);
   return 0;
}

オブジェクトの型

すべてのオブジェクト型は、直接または間接的に Object クラスから継承されます。 前のコードでは明示的に説明されていませんが、 Point クラスは Object から直接継承 します。 このクラスには、すべてのオブジェクトで呼び出すことができるメソッドが多数用意されています。 これらのメソッドを以下に示します。

  • 等しい。 2 つのオブジェクトが等しい場合は true を返します。 サブタイプは、このメソッドをオーバーライドして、ID または等値比較を提供できます。
  • 最終処理を行います。 オブジェクトのメモリが解放される前に、ガベージ コレクターによって呼び出されます。
  • GetHashCode。 オブジェクトのハッシュ コードを返します。
  • GetType。 このオブジェクトの型オブジェクトを返します。 オブジェクトのメタデータへのアクセスを許可します。
  • MemberwiseClone。 オブジェクトの簡易コピーを返します。
  • ToString。 オブジェクトの文字列形式を返します。

Object で定義されているメソッドのほとんどはパブリックです。 ただし、 MemberwiseCloneFinalize は、サブタイプからのみアクセスできるため、保護されたアクセス権を持っています。

次のコードは、 Point 型のオブジェクトで呼び出すことができるパブリック メソッドを示しています。 出力は、.NET Framework SDK で提供される "リフレクター" という単純なプログラムによって生成されました。 リフレクタは 、Point クラスの Type オブジェクトを取得し、そのパブリック メソッドのプロトタイプを表示します。 プロパティ アクセス メソッドとは別に、 Object から継承される 4 つのパブリック メソッドが表示されます。 2 つの保護されたメソッドは表示されません。 出力には、Boolean、Int32、String などの組み込みの型の使用も示されています。

C:\Program Files\NGWSSDK\Samples\Reflector\VC>Reflector -m -v Point
Class: Point
Methods (8)
        Int32 GetHashCode ()
        Boolean Equals (System.Object)
        System.String ToString ()
        Int32 get_X ()
        Void set_X (Int32)
        Int32 get_Y ()
        Void set_Y (Int32)
        System.Type GetType ()

インターフェイス型

インターフェイス型を使用したプログラミングは、非常に強力な概念です。 オブジェクト指向のプログラマは、派生型を基本型に置き換えるという概念に精通しています。 ただし、2 つのクラスが継承に関連しない場合もありますが、共通の機能を共有します。 たとえば、多くのクラスには、永続的ストレージとの間で状態を保存するためのメソッドが含まれている場合があります。 このため、継承に関連しないクラスは共通インターフェイスをサポートする可能性があり、プログラマはクラスの共有動作を、正確な型ではなく、その共有インターフェイスの型に基づいてコーディングできます。

インターフェイス型は、型の部分的な仕様です。 これは、インターフェイスに含まれるメソッドの実装を提供するために実装者をバインドするコントラクトです。 オブジェクト型は多くのインターフェイス型をサポートする場合があり、多くの異なるオブジェクト型は通常、インターフェイス型をサポートします。 定義上、インターフェイス型をオブジェクト型または正確な型にすることはできません。 インターフェイス型は、他のインターフェイス型を拡張できます。

インターフェイスの種類には、次のものが含まれる場合があります。

  • メソッド (クラスとインスタンスの両方)
  • 静的フィールド
  • Properties
  • イベント

インターフェイス型とオブジェクト型 (または値型クラス) の主な違いは、インターフェイス型にインスタンス フィールドを含めることができない点です。

次のコードは、ユーザー定義インターフェイス型 "IPoint" を示しています。このインターフェイス型は、2 つの属性を宣言します (任意の実装で 4 つのアクセサー メソッドを必要とします)。 IPoint から継承することで、 Point クラスはこれら 4 つの抽象メソッドを実装することに同意します。 クラス Point の定義の残りの部分は、「参照型」セクションのコードの定義と変わりません。

#using <mscorlib.dll>

using namespace System;

__gc __interface IPoint
{
   __property int get_X();
   __property void set_X(int value);
   __property int get_Y();
   __property void set_Y(int value);
};

__gc class Point: public IPoint
{
  ...
};

共通言語ランタイムには、さまざまな種類のインターフェイスが用意されています。 次のコードは、配列オブジェクトでサポートされる IEnumerator インターフェイスの使用方法を示しています。 配列を Point*s 使用すると、クライアントは IEnumerator インターフェイスを要求して配列を列挙できます。 IEnumerator インターフェイスでは、次の 3 つのメソッドがサポートされています。

  • 現在の値。 現在のオブジェクトを返します。
  • MoveNext。 列挙子を次のオブジェクトに移動します。
  • リセットします。 列挙子を初期位置にリセットします。
int main(void)
{
  Point *points[] = new Point*[5];
  for(int i = 0, length = points->Count; i < length; i++)
  {
    points[i] = new Point(i, i);
  }
  Collections::IEnumerator *e = points->GetEnumerator();
  while(e->MoveNext())
  {
    Object *o = e->Current;
   Point *p = dynamic_cast<Point*>(o);
   Console::WriteLine(p->X);
  }
  return 0;
}

配列オブジェクトは、 ClearGetLengthSort など、他の多くの便利なメソッドをサポートしています。 配列オブジェクトは、同期のサポートも提供します。 同期は、 IsSynchronizedSyncRoot などのメソッドによって提供されます。

ポインター型

ポインター型は、コードまたは値の場所を指定する手段を提供します。 関数 ポインターはコードを参照します。 マネージド ポインターはガベージ コレクション ヒープ上にあるオブジェクトを指すことができます。 アンマネージド ポインターはローカル変数とパラメーターに対処できます。 これらの異なる型のセマンティクスは大きく異なります。 たとえば、マネージド オブジェクトへのポインターをガベージ コレクターに登録して、オブジェクトがヒープ上で移動されると、ポインターを更新できるようにする必要があります。 ローカル変数へのポインターには、異なる有効期間の問題があります。

ポインターの種類については、ここでは 2 つの理由で詳しく説明しません。 まず、ポインターのセマンティックの問題を理解するには、.NET Framework アーキテクチャをしっかりと理解する必要があります。 第二に、ほとんどのプログラミング言語は、プログラマには見えない程度に存在ポインターを抽象化します。

割り当ての互換性

オブジェクト型とインターフェイス型の概念を説明したので、非常に単純ですが、割り当ての互換性の問題に対処できるようになりました。 参照型の割り当ての互換性の簡単な説明は次のとおりです。

  • T 型の場所 (T はオブジェクト型またはインターフェイス型の場合があります)、T の正確な型を持つ任意のオブジェクトを保持することも、T のサブタイプにすることも、インターフェイス T をサポートすることもできます。

次のコードは、割り当ての互換性の側面を示しています。 Points と Strings は両方ともオブジェクト型であるため、オブジェクトから継承 されます。 したがって、両方とも Object と割り当て互換性があります。 ただし、ポイントと文字列は相互に割り当て互換性がありません。 String クラスは IComparable インターフェイスも実装するため、文字列は IComparable と代入互換ですが、Points は実装されません。

int main(void)
{
  Point* p = new Point();
  String* s = S"Damien";
  Object* o;
  o = p;          // legal, p is an Object
  o = s;          // legal, s is an Object
  p = s;          // illegal, s is not a Point 
  s = p;          // illegal, p is not a String
  IComparable *i;
  i = s;          // legal, Strings support IComparable
  i = p;          // illegal, Points do not support IComparable
  return 0;
}

組み込みの型

組み込みの値型と参照型があります。 組み込みの型には、多くの場合、実行エンジンで効率的に処理できるように、中間言語で特別な命令があります。 組み込み型の例を次に示します。

  • Bool
  • Char (16 ビット Unicode)
  • 符号付き整数と符号なし整数 (8 ビット、16 ビット、32 ビット、64 ビット)
  • 浮動小数点数 (32 ビットおよび 64 ビット)
  • Object
  • String
  • コンピューターに依存する整数 (符号付き整数と符号なし整数の両方)
  • コンピューターに依存する浮動小数点

オブジェクトと文字列は組み込みの参照型ですが、他のすべての組み込み型は値型です。 マシン依存の符号付き整数は、マシンで配列インデックスがサポートできる最大値を保持できます。 コンピューターに依存する符号なし整数は、マシンがサポートできる最大のアドレス値を保持できます。

Metadata

メタデータは、ランタイム型システムと実行エンジンをブリッジする重要なリンクです。 現在、多くのコンポーネント ベースのプログラミング システムには、次の 2 つの重要な問題があります。

  1. コンポーネント (そのメタデータ) に関する情報は、多くの場合、コンポーネントと共に格納されません。 メタデータは、多くの場合、インターフェイス定義言語 (IDL) ファイル、タイプ ライブラリ、インターフェイス リポジトリ、実装リポジトリ、レジストリなどの補助ファイルに格納されます。 .NET 共通言語ランタイムは、この問題を回避するために、型を含むメタデータを格納します。
  2. メタデータ機能はプリミティブです。 ほとんどの機能では、開発者はインターフェイスの構文のみを指定できますが、セマンティクスは指定できません。 この問題の証明は、TINAC の ODL など、多くのシステムで見られる "IDL" 拡張機能の数にあります。 .NET Framework共通言語ランタイムは、カスタム属性と呼ばれる標準化されたメタデータ拡張システムを提供することで、この問題に対処します。

.NET Frameworkを対象とするコンパイラは、次の 2 つの理由から、メタデータを使用して生成される型を記述します。

  1. メタデータを使用すると、ある言語で定義された型を別の言語で使用できます。 この機能により、共通言語ランタイムでの言語の相互運用性が保証されます。
  2. 実行エンジンでは、オブジェクトを管理するためのメタデータが必要です。 オブジェクトの管理には、メモリ管理などの要件が含まれます。

メタデータは、バイナリ形式でファイルに格納されます。 この形式は指定されていないため、開発者はバイナリ レイアウトに依存しないでください。 代わりに、メタデータの読み取りと書き込みのためのメソッドが多数あります。

メタデータ拡張

.NET Frameworkのメタデータ機能は、カスタム属性を使用して拡張できます。 次の C# コードは、カスタム属性の使用と言語の相互運用性を示しています。 次のコードで定義されている最初のクラス Author, はカスタム属性であり、継承仕様によって指定されます。 このクラスには、作成者の名前を表す String という 1 つのフィールドが含まれています。 コンストラクターを含む 2 つのメソッドが含まれており、 その ToString メソッドをオーバーライドします。

using System.Reflection;

public class Author: Attribute
{
  using System;
  public readonly string name;
  public Author(string name) 
  {
   this.name = name;
  }
  public override String ToString() 
  {
    return String.Format("Author: {0}", name);
  }
}

[Author("Damien Watkins")]
public class CSPoint: Point
{
  public static void Main()
  {
    MemberInfo info = typeof(CSPoint); 
    object[] attributes = info.GetCustomAttributes();
    Console.WriteLine("Custom Attributes are:");
    for (int i = 0; i < attributes.Length; i++) 
    {
      System.Console.WriteLine("Attribute " 
         + i + ": is " + attributes[i].ToString());
    }
  }
}

この図で定義されている次のクラスは、C++ Point クラスを継承する C# クラスです。 クラスの定義の前には、このクラスに Author 型のカスタム属性があることを示す仕様が付 けられますCSPoint クラスの Main メソッドは、GetCustomAttributes メソッドを使用して、このクラスのカスタム属性の配列にアクセスし、画面に表示します。

アセンブリとマニフェスト

アセンブリは、共通言語ランタイム型とコードのコレクションです。 アセンブリは、関連するコンポーネントをグループ化し、副作用として名前空間に配置する手段です。 .NET Framework型が自己記述型であるのと同様に、アセンブリも自己記述型です。 アセンブリに関する情報は、そのマニフェストに含まれています。 マニフェストには次のものが含まれます。

  • 名前とバージョンの情報
  • アセンブリ内の型とその場所の一覧
  • 依存関係の情報
#using <mscorlib.dll>

using namespace System;
using namespace System::Reflection;

int main(void)
{
   Type *t= Type::GetType("System.String");
   Assembly *a = Assembly::GetAssembly(t);
   AssemblyName *an = a->GetName(false);

   Console::WriteLine("AssemblyName->Name: {0}", an->Name);
   Console::WriteLine("AssemblyName->Codebase:\n\t {0}", an->CodeBase);

   String *rn[] = a->GetManifestResourceNames();
   Console::WriteLine("Resource Name(s):");
   for(int i = 0; i < rn->Count; i++)
      Console::WriteLine(S"\t {0}", rn[i]);
   
   Module *m[] = a->GetModules();
   Console::WriteLine("Module Name(s):");
   for(int i = 0; i < m->Count; i++)
      Console::WriteLine(S"\t {0}", m[i]->FullyQualifiedName);

   return 0;
}

上記のコードは、アセンブリの一部の側面を示しています。 このプログラムは、最初に String クラスの型オブジェクトを取得し、その型オブジェクトを使用してそのアセンブリ情報にアクセスします。 次のコードは、このプログラムから生成された出力を示しています。

C:\Project7\Exercises\assembly>StringAssembly
AssemblyName->Name: mscorlib
AssemblyName->Codebase:
         file://C:/WINNT/ComPlus/v2000.14.1809/mscorlib.dll
Resource Name(s):
         mscorlib.resources
         prcp.nlp
         sortkey.nlp
         ctype.nlp
         sorttbls.nlp
         culture.nlp
         l_except.nlp
         bopomofo.nlp
         region.nlp
         xjis.nlp
         big5.nlp
         l_intl.nlp
         ksc.nlp
         charinfo.nlp
         prc.nlp
Module Name(s):
         C:\WINNT\ComPlus\v2000.14.1809\mscorlib.dll

実行システム

共通言語ランタイム実行エンジンは、コードが必要に応じて実行されるようにする役割を担います。 実行エンジンは、次のような MSIL コードの機能を提供します。

  • コードの読み込みと検証
  • 例外管理
  • ジャストインタイム コンパイル
  • メモリ管理
  • セキュリティ

中間言語

.NET Framework準拠コンパイラは、ソース コードを MSIL 命令とメタデータに変換します。 MSIL 命令はアセンブリ言語命令に似ていますが、このコードは 1 つの物理 CPU に固有ではありません。 代わりに、MSIL はネイティブ コードへのセカンダリ コンパイルを行います。 MSIL はコンピューターに依存しないため、コンピューターからマシンに移動できます。 MSIL からネイティブ コードへの変換は、コードの実行まで、任意の段階で実行できます。

実行エンジンは、多数の Just-In-Time (JIT) コンパイラを提供します。 MSIL はネイティブ コードにコンパイルできます。

  • マシンに初めてインストールされたとき。

    または

  • メモリに読み込まれた後、実行される前。

次のコードは、 Point クラスのサンプル MSIL コードを示しています。 MSIL は直感的で、次の情報を見つけることができます。

  • クラス (Point) の名前と System.Object からの継承。
  • クラスのフィールドの定義 (m_xとm_y)。
  • コンストラクター (.ctor) とその他のメソッド (get_Xとset_X) の定義。
...
.class auto ansi Point extends ['mscorlib']System.Object
{
  .field private int32 m_x
  .field private int32 m_y
  .method public specialname rtspecialname 
          instance void .ctor() il managed
  {
    // Code size       21 (0x15)
    .maxstack  2
    IL_0000:  ldarg.0
    IL_0001:  call       instance void ['mscorlib']System.Object::.ctor()
    IL_0006:  ldarg.0
    IL_0007:  ldc.i4.0
    IL_0008:  stfld      int32 Point::m_y
    IL_000d:  ldarg.0
    IL_000e:  ldc.i4.0
    IL_000f:  stfld      int32 Point::m_x
    IL_0014:  ret
  } // end of method 'Point::.ctor'
  .method public instance int32 get_X() il managed
  ...
  .method public instance void  set_X(int32 'value') il managed
    .property int32 Y()
  {
    .set instance void set_Y(int32)
    .get instance int32 get_Y()
  } // end of property 'Point::Y'
  ...
...

マネージドとアンマネージド

共通言語ランタイムでは、 マネージドアンマネージド という用語を使用して、コード、データ、ポインターを修飾します。 マネージドまたはアンマネージドは、ランタイムがプログラムの側面に対して持つコントロールの量を識別します。 管理されるものは、共通言語ランタイムによって厳密に制御されます。

マネージド コードは、実行エンジンが実行を安全に管理できるようにするための十分な情報 (メタデータ) を実行エンジンに提供します。 セーフ実行には、デバッグ、言語間相互運用性、メモリ管理、セキュリティなどのプログラム実行の側面が含まれます。 アンマネージド コードでは、このような情報は実行エンジンに提供されません。 実行エンジンは、アンマネージ コードに対してこれらのサービスを提供できません。 現在、Windows プラットフォームで実行されているコードの大部分は管理されていません。

マネージド コードとアンマネージド コードが同じプログラムに存在する可能性があるという事実は、必ずしも問題ではありません。 ただし、マネージド コードとアンマネージド コードの間の相互作用には細心の注意が必要です。 たとえば、ガベージ コレクターは、すべてのスレッドが中断されている場合にのみ実行できます。 スレッドがアンマネージ コードを実行すると、実行エンジンはスレッドを中断できず、ガベージ コレクションは実行できません。 もう 1 つの懸念事項は、例外処理です。 マネージド コードは、ネイティブ コードが WIN32 例外をスローする可能性がある一方で、.NET Framework共通言語ランタイム例外をスローします。 マネージド コードとアンマネージド コードの間で例外が伝達される場合は、想定されるモデルに合わせて例外を変更する必要があります。

マネージド データは、共通言語ランタイムによってガベージ コレクションヒープに割り当てられる値を記述します。 マネージド データに到達できるのはマネージド コードのみですが、アンマネージド データはマネージド コードとアンマネージド コードの両方で使用できます。

共通言語仕様

共通言語ランタイムを多くのプログラミング言語機能の和集合として記述できる場合、共通言語仕様 (CLS) はこれらの機能のサブセットです。 サブセットは、すべての言語に共通するすべての機能のセットではありません。 むしろ、多くのプログラミング言語に共通する一連の機能です。

CLS が必要な理由 主な理由は、ほとんどのプログラミング言語で、すべてのランタイム機能をサポートしたくない場合があるためです。 CLS は、コンパイラ ライターが相互運用性を求める場合に、ほとんどの言語で達成できるコンプライアンス レベルを表します。 この機能サブセットは、Active Server Pages+ (ASP+) など、多くの.NET Framework準拠ツールがサポートを目指すレベルでもあります。

共通言語ランタイムと比較した場合の CLS のサイズは重要な問題です。 定義上、CLS はランタイムより大きくすることはできません。 ただし、CLS が小さすぎると、役に立つ操作を行うのに十分な機能が提供されません。 現在、CLS は不要なランタイムよりも多くのランタイムをサポートしているように見えます。 この観点から、ランタイムから何を排除するかという点で CLS について説明する方が適切です。

まとめ

この記事では、.NET Frameworkのコア コンポーネントの概要について説明しました。 言語統合は中心的なテーマです。 共通言語ランタイムは、プログラマが 1 つの言語で型とライブラリを使用して定義し、これらの型を他の言語でシームレスに使用できるように、プログラム言語の統合を容易にします。

謝辞

この記事は、Mark Hammond と私が Microsoft .NET で書いている今後の書籍に基づいています。 私たちは道に沿って私たちを助けた多くの人々を認めなければなりません。 技術への早期アクセスを提供してくれたジェームズ・プラモンドンに感謝します。ブラッド・エイブラムスとジム・ミラーは、.NET Frameworkに関する多くの議論と明確化のために、すべての細部に熱心に出席するジョン・ニッポンスキーとスティーブ・クリスチャンソン、そして会議での会話が本当にアイデアを開いた他のすべてのProject 7人のメンバーに。 最後に、アーキテクチャ、コンパイラ、およびオンライン ドキュメントを設計して構築したすべてのユーザーに感謝し、最終的に.NET Frameworkを理解することができました。 この記事では、他の開発者が.NET Frameworkにブートストラップできることを願っています。