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

 

Damien Watkins
モナッシュ大学

2000 年 10 月

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

内容

概要 共通言語ランタイム
割り当ての互換性
メタデータ 共通言語仕様の 結論

はじめに

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

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

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

共通言語ランタイム

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

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

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

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

図 1 は、ランタイムの要素間のリレーションシップの 1 つのビューを示しています。 図の上部にあるソース ファイルには、C++ などの多くの言語で新しい型の定義が保持されている場合があります。 この新しい型は、.NET Framework ライブラリの型 (Object など) から継承されます。 このファイルが.NET Framework C++ コンパイラによってコンパイルされると、結果の Microsoft Intermediate Language (MSIL) は、新しい型のメタデータと共にファイルに保持されます。 使用されるメタデータ形式は、型が定義されたプログラミング言語とは無関係です。 この新しい型の MSIL が存在すると、C#、エッフェル、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 バイト以下です。 ユーザー定義の構造体とクラスには値型を指定でき、次を含めることができます。

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

基本的なレベルでは、プロパティとイベントはどちらもメソッドと同じになります。 プロパティは構文のショートカットです。これらは、クラスの論理フィールドの 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 または等価比較を提供するために、このメソッドをオーバーライドできます。
  • Finalize。 オブジェクトのメモリが再利用される前に、ガベージ コレクターによって呼び出されます。
  • GetHashCode。 オブジェクトのハッシュ コードを返します。
  • GetType。 このオブジェクトの型オブジェクトを返します。 オブジェクトのメタデータへのアクセスを許可します。
  • MemberwiseClone。 オブジェクトの簡易コピーを返します。
  • ToString。 オブジェクトの文字列形式を返します。

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

次のコードは、 Point 型のオブジェクトで呼び出すことができるパブリック メソッドを示しています。 出力は、.NET Framework SDK で提供される "Reflector" という単純なプログラムによって生成されました。 リフレクターは 、Point クラスの Type オブジェクトを取得し、そのパブリック メソッドのプロトタイプを表示します。 プロパティ アクセス メソッドとは別に、 Object から継承される 4 つのパブリック メソッドが表示されます。 2 つの保護されたメソッドは表示されません。 出力には、ブール値、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 つのクラスが継承によって関連付けられない場合もありますが、共通の機能を共有します。 たとえば、多くのクラスには、永続的ストレージとの間で状態を保存するためのメソッドが含まれている場合があります。 このため、継承に関連しないクラスは共通インターフェイスをサポートする可能性があり、プログラマは、クラスの共有動作を、正確な型ではなく、共有インターフェイス型に基づいてコーディングできます。

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

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

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

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

次のコードは、ユーザー定義インターフェイス型 "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 つのメソッドがサポートされています。

  • Current。 現在のオブジェクトを返します。
  • 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 から継承 されます。 したがって、どちらも 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
  • コンピューター依存の整数 (符号付きと符号なしの両方)
  • コンピューターに依存する浮動小数点

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 クラスの型オブジェクトを取得し、次に type オブジェクトを使用してそのアセンブリ情報にアクセスします。 次のコードは、このプログラムから生成された出力を示しています。

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 つの懸念事項は、例外処理です。 マネージド コードでは、.NET Framework共通言語ランタイム例外がスローされますが、ネイティブ コードでは WIN32 例外がスローされる可能性があります。 マネージド コードとアンマネージド コードの間で例外が伝達される場合は、想定されるモデルに合わせて例外を変更する必要があります。

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

共通言語仕様

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

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

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

まとめ

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

謝辞

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