KEN's .NET

[IL1] はじめの一歩

ホーム > KEN's .NET > [IL1] はじめの一歩

ここでは.NET Frameworkのアセンブリ言語MSIL(Microsoft Intermediate Language)を使ったプログラミングを紹介します。 読者は.NET プログラミング経験があることを想定しています。

1. ILって?

.NET Frameworkで動作するプログラムを作る場合、一般的にはVB.net(以降VB)やC#に代表される高級言語で開発を行います。 各言語のコンパイラによって作成されるEXE、DLLファイルの中身は、 どの言語で作成した場合でも、.NETの仮想マシン向けのオブジェクトコード(機械語)になります。(図1)

.NETでのコンパイルを説明
図1 .NETにおけるコンパイル

例えば、Windowsネイティブ用のコンパイラであれば、Windows用の特定のCPU向けのオブジェクトコードを生成します。 非.NET言語用コンパイラは「特定OSの特定CPU」が理解できるオブジェクトコードを生成するのに対して、 .NET言語用コンパイラは「仮想マシン」が理解できるオブジェクトコードを生成するということがポイントです。
※ 非.NET言語にもJava言語のように仮想マシン向けのオブジェクトコードを生成するものはあります。(この場合はもちろんJava用仮想マシン向けです)

さて、ようやく本題のIL(Intermediate Language)とは何か?という話になりますが、 .NETのオブジェクトコードと1対1に対応したアセンブリ言語です。 実際のオブジェクトコードはバイナリデータであり、そのまま人が読んで理解することは困難ですが、 ILはこのオブジェクトコードを人が読める形式で表現したものということになります。 Languageという言葉の通り、このILを使って直接プログラミングをすることができます。 以降の章では具体例を示しながら、ILプログラミングを行いたいと思います。

ちなみにILという言語を覚えると以下のような点でお得です。

  • 私はILで.NETプログラム作れるんですよ、と自慢できる。
  • VB/C#などで作成したプログラムの動作を解析するとき、ildasmツールで逆アセンブルしたILを読んで理解できる。
  • いつかは.NET向けプログラミング言語を考案してコンパイラを作ってみたい!という場合に、ILの知識が役に立つ。

2. 最初のILプログラミング

「Hello world!」をコンソールに表示するプログラムを作ります。 プログラミングに入る前に、ILコードをilasm.exeでアセンブルする方法とアセンブルして作成される実行ファイルを実行する方法を説明します。 ilasm.exeとは、VB/C#におけるコンパイラ(VBでのvbc.exe、C#でのcsc.exe)にあたるものです。 また、オブジェクトコードへの変換を行う作業をアセンブルといいます。高級言語におけるコンパイルにあたります。

これから作るプログラムのソースコードはhello.ilという名前でファイルに保存するものとします。DOSプロンプトを開き、図2のようにコマンドを実行します。 カレントディレクトリを.NET Framework SDKか.NET Framework ランタイムのあるディレクトリ(例えば、"C:\WINDOWS\Microsoft.NET\Framework\(フレームワークのバージョン)"のようなilasm.exeのある場所)に移動しておきます。 自分の環境でilasm.exeのある場所がわからない場合はエクスプローラの「ファイル検索」などで探してください。

> ilasm xxxx\hello.il
図2 アセンブルの仕方

ilasmがILコードをアセンブルするためのコマンドで、図2のように hello.ilのフルパス(xxxxはhello.ilのパスを表しているものとします)を指定してください。 実行するとxxxxフォルダにhello.exeが作成されます。実行すると図3のような実行結果が表示されます。

> xxxx\hello.exe
Hello world!

図3 hello.exeの実行結果

いよいよ、本題のHello world!を表示するプログラムです。 図4のILのソースコードをご覧ください。最初の一歩ということで、厳密さは重要ではないので、、 ilasm.exeでエラーとならない範囲で不要なソースコードは省略しています。

図4 Hello world!を表示するプログラム
1
2
3
4
5
6
7
8
9
10
/* アセンブルの方法
   (コマンドラインから) */
//  → ilasm (hello.ilのパス)hello.il
.assembly hello {}
.method public static void main()
{
	.entrypoint
	ldstr "Hello world!"
	call void [mscorlib]System.Console::WriteLine(string)
	ret
}

このILコードをhello.ilに保存して、アセンブルを行って、実行してみてください。 実行して「Hello world!」と表示されることを確認できたら、 次はILコードを詳細に見ていきましょう。ポイントは以下の通りです。

  • 1、2行目よりC#風の形式でコメントを記述できることがわかります。 /*〜*/で複数行コメント、//で1行コメントです。
  • 3行目の.assembly hello {} でアセンブリ(EXEやDLL)の名前をhelloとしています。 {}の中にはアセンブリに関する情報を書きますが、今のところ不要なため省略しています。
  • 4行目の.method で始まる行でmainメソッドを定義しています。C#と同じような書き方になっています。
    • public(全体に公開)
    • static(静的メソッド)(VBではSharedにあたる)
    • void(戻り値なし)(VBではSubプロシージャにあたる)
  • 6行目の.entrypointはmainメソッドがプログラムの開始点(エントリーポイントと言います)であることを指定しています。 C#/VBではMainメソッド(先頭大文字です!)がエントリーポイント用のメソッドと決まっていますが、 ILコードでは、.entrypointキーワードで明示的に指定します。 また、C#/VBではクラス(VBの場合はモジュールも含む)の中にメソッドを記述しますが、ILコードでは、 クラスは必須ではありません。このようなメソッドをグローバルメソッドと呼びます。
  • 7、8行目はプログラムの核心部分です。
    • 7行目のldstr(load stringの意)で後ろに指定している"Hello world!"という文字列をロードします。
    • 8行目で先ほどロードした文字列を使って、System.ConsoleクラスのWriteLineメソッドを呼び出しています。 callはメソッドの呼び出しを行う命令です。[mscorlib]でSystem.Consoleクラスがmscorlib.dllにあることを指示しています。 System.Console::WriteLineという風にクラス名とメソッド名の区切りは::を使うところはC++風です。
  • 9行目のret(returnの意)でメソッドから戻ること指定します。メソッドの最後には必須です。 無くてもアセンブルはできますが、実行時にInvalidProgramExceptionの例外が発生し強制終了します。

3. スタックマシン

2章の図4のhello.ilのILコードの核心部分の7、8行目の説明はさらっと流していますが、 ILプログラミングにおいて最も大事な内容が含まれています。 7行目の説明でldstrで文字列をロードすると説明しましたが、いったいどこにロードするのでしょうか? 答えは「.NET仮想マシンが持つスタックへロードされる」となります。 (ここまでの説明でなるほどねと思った方は続きの説明を読む必要はないかもしれません。)

スタックとは、配列のように複数のデータを扱うためのデータ構造の1つです。 ただし、配列とは異なり、皿をテーブルの上に積み上げたような構造をしています。 積み上がった皿を使うときは上の皿から順番に降ろして使いますね。 つまり、最後に積み上げた皿から使っていくことになります。 こういったデータ構造はLIFO(Last In First Out)と呼ばれることがあります。 データの追加(push)/取り出し(pop)のみ行うことができ、途中のデータをいきなり取り出すことはできません。 データの追加は最上段に積み上げていく形で追加し、データの取り出しは最上段のものから一つずつ取り出します。

.NET仮想マシンではVB/C#などにおける一時的な変数の役割を担う機構として、 スタックを持っています。このような仮想マシンをスタックマシンと言います。 簡単な足し算を例に説明します。スタックマシンで5+9を計算する場合、 図5のようなスタック操作を行うことになります。

スタックによる足し算
図5 スタックによる足し算

この手順を元にhello.ilの7、8行目を書き換えると図6のようになります(図6にはソースコードで7、8行目を書き換える分のみ掲載)。 ldc.i4命令(load constant integer 4 byteの意)は32ビット整数の定数をスタックにロードする命令です。

図6 5+9の結果を表示するプログラム(hello.ilからの差分)
1
2
3
4
	ldc.i4 5
	ldc.i4 9
	add
	call void [mscorlib]System.Console::WriteLine(int32)

4. 学んだこと

  • ILとは.NETのオブジェクトコードと1対1に対応したアセンブリ言語
  • .NET仮想マシンはスタックマシン
  • ILプログラミングの枠組み
    • .assembly 〜 {} でアセンブリ名を指定する
    • .method 〜 でメソッドを定義する。
    • public static void なんてキーワードはC#と同じ
    • .entrypointでエントリーポイントになるメソッドを指定する。
    • クラスに所属しないグローバルメソッドを定義できる。
    • ldstrで文字列をロード、ldc.i4で32ビット整数をロードする。
    • call命令でメソッドを呼び出せる。System.Console::WriteLineを呼んでみた。
    • retでメソッドを抜けるらしい(必須!)

A. サンプルダウンロード

B. 参考文献/URL

言い訳。参考URLのサイト:Atelier Blueと今回のページで内容かぶっている部分がありますが、 当ページも一から書き下ろしたもので、ぱくりではありません。(Hello World!やスタックマシンは避けて通れないので仕方ないんだ!という言い訳です(^^;)) 今後の連載では、なるべく内容がかぶらないようにしたいと思っておりますので、ご容赦ください。

ホーム > KEN's .NET > [IL1] はじめの一歩

[e-mail] yone_ken00@hotmail.com