共通中間言語( CIL ) は、以前はMicrosoft 中間言語( MSIL ) または中間言語( IL ) と呼ばれていましたが、[ 1 ]は共通言語インフラストラクチャ(CLI) 仕様で定義されている中間言語バイナリ命令セットです。 [ 2 ] CIL 命令は、共通言語ランタイムなどの CIL 互換ランタイム環境によって実行されます。CLI を対象とする言語は CIL にコンパイルされます。CIL はオブジェクト指向のスタックベースのバイトコードです。ランタイムは通常、CIL命令をネイティブコードにジャストインタイムでコンパイルします。
CIL は、.NET 言語のベータ版リリース時には Microsoft Intermediate Language (MSIL) として知られていました。C #と CLI の標準化により、バイトコードは現在正式に CIL として知られています。[ 3 ] Windows Defender のウイルス定義では、引き続き CIL でコンパイルされたバイナリを MSIL と呼んでいます。[ 4 ]
CLI プログラミング言語のコンパイル時には、ソースコードはプラットフォームやプロセッサ固有のオブジェクト コードではなく、CIL コードに変換されます。CIL はCPUおよびプラットフォームに依存しない命令セットであり、 Windowsの.NET ランタイムやクロスプラットフォームのMonoランタイムなど、共通言語インフラストラクチャをサポートするあらゆる環境で実行できます。理論的には、これにより、異なるプラットフォームや CPU タイプ用に異なる実行可能ファイルを配布する必要がなくなります。CIL コードは実行時に安全性が検証されるため、ネイティブにコンパイルされた実行可能ファイルよりも優れたセキュリティと信頼性が提供されます。[ 5 ] [ 6 ]
実行プロセスは次のようになります。
CILバイトコードには、以下のタスクグループに対する命令が含まれています。
共通中間言語はオブジェクト指向かつスタックベースであり、これは、ほとんどのプログラミング言語のように複数のレジスタや他のメモリ位置ではなく、命令パラメータと結果が単一のスタック上に保持されることを意味します。
eaxとedxが2つの異なる汎用レジスタを指定する、 x86アセンブリ言語で2つの数値を加算するコード:
eax 、edxを追加中間言語(IL)でコードを記述してください。0はeax、1はedxを表します。
ldloc .0 // ローカル変数 0 をスタックにプッシュldloc .1 // ローカル変数 1 をスタックにプッシュadd // スタックの上位 2 つの要素をポップして加算し、結果をスタックにプッシュstloc .0 // スタックの上位要素をポップしてローカル変数 0 に格納後者の例では、まず2つのレジスタeaxとedxの値がスタックにプッシュされます。加算命令が呼び出されると、オペランドが「ポップ」され(取得され)、結果がスタックに「プッシュ」されます(格納されます)。その後、結果の値がスタックからポップされ、eaxに格納されます。
CILはオブジェクト指向設計です。オブジェクトを作成したり、メソッドを呼び出したり、フィールドなどの他の種類のメンバーを使用したりできます。
すべてのメソッドは(いくつかの例外を除いて)クラス内に存在する必要があります。この静的メソッドも同様です。
. class public Foo { . method public static int32 Add ( int32 , int32 ) cil managed { . maxstack 2 ldarg .0 // 最初の引数をロードします。 ldarg .1 // 2 番目の引数をロードします。add // それらを加算します。ret // 結果を返します。} }Add メソッドは static として宣言されているため、Foo のインスタンスを宣言する必要はありません。C# では次のように使用できます。
int r = Foo.Add ( 2 , 3 ) ; // 5CILでは、次のようになります。
ldc.i4.2 ldc.i4.3 call int32 Foo :: Add ( int32 , int32 ) stloc.0インスタンスクラスは、少なくとも1つのコンストラクタといくつかのインスタンスメンバを含みます。以下のクラスは、Carオブジェクトの動作を表す一連のメソッドを持っています。
. class public Car { . method public specialname rtspecialname instance void . ctor ( int32 , int32 ) cil managed { /* コンストラクタ */ }. method public void Move ( int32 ) cil managed { /* 実装を省略 */ } . method public void TurnRight () cil managed { /* 実装を省略 */ } . method public void TurnLeft () cil managed { /* 実装を省略 */ } . method public void Brake () cil managed { /* 実装を省略 */ } }C#では、クラスインスタンスは次のように作成されます。
Car myCar = new Car ( 1 , 4 ); Car yourCar = new Car ( 1 , 3 );そして、これらの記述は、CILにおける以下の指示とほぼ同じです。
ldc . i4 .1ldc . i4 .4newobj instance void Car ::. ctor ( int32 , int32 )stloc .0 // myCar = 新しい Car(1, 4);ldc . i4 .1ldc . i4 .3newobj instance void Car ::. ctor ( int32 , int32 )stloc .1 // yourCar = new Car(1, 3);C#では、インスタンスメソッドは次のように呼び出されます。
myCar.Move ( 3 ) ;CILで呼び出される場合:
ldloc .0 // オブジェクト "myCar" をスタックにロードしますldc . i4 .3インスタンスを呼び出しますvoid Car :: Move ( int32 )共通言語インフラストラクチャ(CLI)は、コンパイル済みクラスに関する情報をメタデータとして記録します。コンポーネントオブジェクトモデルの型ライブラリと同様に、これによりアプリケーションはアセンブリ内のインターフェース、クラス、型、メソッド、フィールドをサポートおよび検出できます。このようなメタデータを読み取るプロセスは、リフレクションプログラミング(反射)の一部です。
メタデータは属性の形式で表すことができます。属性はクラスを拡張することでカスタマイズ可能ですAttribute。これは強力な機能です。クラスの作成者は、アプリケーションのドメインに応じて、クラスの利用者がさまざまな有意義な方法で使用できる追加情報をクラスに付加することができます。
以下は、CILアセンブラで書かれた基本的な「Hello, World!」プログラムです。このプログラムは「Hello, world!」という文字列を表示します。
.assembly Hello {} .assembly extern mscorlib { } .method static void Main ( ) { .entrypoint .maxstack 1 ldstr " Hello , world ! " call void [ mscorlib ] System.Console :: WriteLine ( string ) ret }以下のコードは、オペコードの数が多いため、より複雑です。
このコードは、 JVMバイトコードに関する記事の対応するコードと比較することもできます。
static void Main ( string [ ] args ) { for ( int i = 2 ; i < 1000 ; i ++ ) { for ( int j = 2 ; j < i ; j ++ ) { if ( i % j == 0 ) goto outer ; } Console.WriteLine ( i ) ; outer :; } }CILアセンブラ構文では、次のようになります。
. method private hidebysig static void Main ( string [] args ) cil managed { . entrypoint . maxstack 2 . locals init ( int32 V_0 , int32 V_1 )LDC 。i4 .2 stloc .0 br 。■ IL_001f IL_0004 : ldc 。i4 .2 stloc .1 br 。■ IL_0011 IL_0008 : ldloc .0 ldloc .1 rem brfalse 。■ IL_001b ldloc .1 ldc 。i4 .1 stloc .1を追加IL_0011 : ldloc .1 ldloc .0 blt 。■ IL_0008 ldloc .0コールvoid [ mscorlib ]システム。コンソール:: WriteLine ( int32 ) IL_001b : ldloc .0 ldc 。i4 .1 stloc .0 IL_001f : ldloc .0 ldcを追加します。i4 0x3e8 blt 。IL_0004戻ります}これは、CILが仮想マシン(VM)レベル付近でどのように見えるかを示す一例です。コンパイル時には、メソッドはテーブルに格納され、命令はアセンブリ内のバイト列として格納されます。アセンブリはポータブル実行可能ファイル(PE)です。
CILアセンブリと命令は、コンパイラまたは実行環境に付属するILアセンブラ(ILAsm )と呼ばれるユーティリティによって生成されます。
アセンブルされた CIL は、 IL 逆アセンブラ(ILDASM)を使用して再びコードに逆アセンブルすることもできます。.NET Reflectorなどの他のツールは、CIL を高水準プログラミング言語(C# やVisual Basicなど)に逆コンパイルできます。このため、CIL はリバースエンジニアリングの非常に容易なターゲットとなります。この特性はJVM バイトコードにも共通しています。ただし、コードを難読化し、容易には読み取れないが実行可能な状態にするツールも存在します。
ジャストインタイムコンパイル(JIT)とは、バイトコードをCPUが即座に実行可能なコードに変換する処理です。この変換は、プログラムの実行中に段階的に行われます。JITコンパイルは、環境固有の最適化とアセンブリ検証を提供します。これを実現するために、JITコンパイラはアセンブリメタデータを検査して不正アクセスがないかを確認し、違反があった場合は適切に処理します。
CLI互換の実行環境には、アセンブリの事前コンパイル(AOT)を実行するオプションも用意されており、実行時にJIT処理を排除することで、実行速度を向上させることができます。
.NET Frameworkには、AOTコンパイルを実行するNative Image Generator (NGEN)と呼ばれる特別なツールがあります。AOTコンパイルの別のアプローチとしてCoreRTがあり、これはランタイムに依存することなく.NET Coreコードを単一の実行可能ファイルにコンパイルできます。MonoもAOTコンパイルをサポートしています。
Javaのバイトコードとの顕著な違いは、CILには、C/C++コードをCILにコンパイルするために必要なデータ/関数ポインタ操作に十分な、、、、および多くの呼び出し命令が含まれていることldindです。stindldloca
class A { public : virtual void __stdcall meth () {} }; void test_pointer_operations ( int param ) { int k = 0 ; int * ptr = & k ; * ptr = 1 ; ptr = & param ; * ptr = 2 ; A a ; A * ptra = &a a ; ptra -> meth (); }CILにおける対応するコードは、次のように表現できます。
.メソッドアセンブリstatic void modopt ([ mscorlib ] System . Runtime . CompilerServices . CallConvCdecl ) test_pointer_operations ( int32 param ) cil managed { . vtentry 1 : 1 // コード サイズ 44 (0x2c) . maxstack 2 . locals ([ 0 ] int32 * ptr , [ 1 ] valuetype A * V_1 , [ 2 ] valuetype A * a , [ 3 ] int32 k ) // k = 0; IL_0000 : ldc . i4 .0 IL_0001 : stloc .3 // ptr = &k; IL_0002 : ldloca . s k // ローカルのアドレスをロードする命令IL_0004 : stloc .0 // *ptr = 1; IL_0005 : ldloc .0 IL_0006 : ldc .0 i4 .1 IL_0007 :標準。i4 // 間接命令// ptr = ¶m IL_0008 : ldarga . s param // パラメータのアドレス命令をロードIL_000a : stloc .0 // *ptr = 2 IL_000b : ldloc .0 IL_000c : ldc . i4 .2 IL_000d :標準。i4 // a = 新しい A; IL_000e : ldloca 。s a IL_0010 : call valuetype A * modopt ([ mscorlib ] System . Runtime . CompilerServices . CallConvThiscall ) ' A .{ ctor } ' ( valuetype A * modopt ([ mscorlib ]システム。ランタイム。コンパイラサービス。IsConst ) modopt ([ mscorlib ] System . Runtime . CompilerServices . IsConst )) IL_0015 : Pop // ptra = &a; IL_0016 : ldloca 。IL_0018 : stloc .1 // ptra->meth( ) ; IL_0019 : ldloc .1 IL_001a :複製IL_001b : ldind 。i4 // 仮想呼び出しIL_001c : ldindの VMT を読み取ります。i4 IL_001d : calli unmanaged stdcall void modopt ([ mscorlib ] System . Runtime . CompilerServices . CallConvStdcall )( native int ) IL_0022 : ret } // メソッド 'Global Functions'::test_pointer_operations の終わり: ... .NET プロジェクトをコンパイルすると、バイナリ コードに直接変換されるのではなく、中間言語に変換されます。プロジェクトを実行すると、.NET プログラミングのすべての言語が CIL のバイナリ コードに変換されます。実行時に必要な CIL の一部のみがバイナリ コードに変換されます。.NET の DLL と EXE も CIL 形式です。