dokyouのブログ -2ページ目

第3回 .NETアプリケーションが実行される仕組み

 前回 は、Windows OS上で.NETアプリケーションが起動する仕組みとして、.NETアプリケーションがWin32アプリケーションとして動作している部分の処理の流れを説明した。そしてそのWin32の世界から.NETの世界への入り口として、最終的にmscoree.dllの_CorExeMain関数が呼び出されることが分かった。

 今回はこの_CorExeMain関数から、どのようにして.NETアプリケーションが、その実行環境である(.NET Frameworkの)CLR(Common Language Runtime:共通言語ランタイム) 上で動作し始めるのか、その仕組みを解説する。具体的には、

  • 「mscoree.dllの_CorExeMain関数」から

↓

  • 「.NETアプリケーションの次のMainメソッドのコード(C#)」まで
    static void Main()
    {
    Application.Run(new Form1());
    }

の実行ステップを明らかにする。

 本稿では前回と同じ、次のようなシンプルなWindowsアプリケーション(WindowsApplication1.exe)をサンプルとして使用する。この.NETアプリケーションが、CLR上で起動する際の処理の流れをつぶさに追いかける。

本稿で使用するサンプル・アプリケーション
本稿では、「WindowsApplication1.exe」というシンプルなWindowsアプリケーションをサンプルとして使用する。この.NETアプリケーションが、CLR上で起動するときの処理の流れを解説する。
エクスプローラに表示されている「WindowsApplication1.exe」をダブルクリックする。
Windows OSのデスクトップ画面上に、「Form1」というタイトルでアプリケーション・ウィンドウが表示される。

 それでは_CorExeMain関数以降の実行処理の流れに入っていきたいが、その前提知識としてあらかじめ.NETプログラムの内部構造について知っておいた方が理解が早い。そこで本稿では、まずこの.NETプログラムの構造について概説しておくことにしよう。

 なお本稿で提示されている図版などは簡略化しており、内部構造のすべてを詳細かつ正確に明記しているわけではないことをあらかじめお断りしておく。

.NETプログラムの構造(箱と本体)

 .NETプログラムはWindows上の「ファイル」に格納されており、そのファイルはWindows OS(Win32システム)が認識可能な「PE(Portable Execute)フォーマット」である、と前回 説明した。

 当然ながら.NETプログラムの実体となるコードも、このPEフォーマットにのっとってファイル内部に格納されているはずだ。それではその内部構造はどのようになっているのだろうか。

●.NETプログラムにおけるPEフォーマットの構造

 そこで、その内部構造を(簡略化して)示したものが次の図と表である。

(.NETプログラムにおける)PEフォーマットの構造
項目 説明
PEヘッダ

<Win32プログラム>のエントリ・ポイントへのアドレスなどが

格納されている。前回で解説済み

Win32プログラム

・コード

<.NETプログラム>のエントリ・ポイントへのジャンプ命令が

格納されている。具体的なジャンプ先は、 mscoree.dllの_

CorExeMain関数か、もしくは_CorDllMain関数である。

これについても前回で解説済み

CLRヘッダ

「CLI(Common Language Infrastructure)ヘッダ」とも呼ばれる。

CLRデータに関するヘッダ情報として

「 マニフェスト」が格納されている

CLRデータ

「CLIデータ」とも呼ばれる。.NETプログラムの実体として

「 型メタデータ」「 ILコード」「 マネージ・リソース」が

格納されている

ここで示した内部構造は分かりやすくするために簡略化して表したものなので、

厳密な構造(および位置関係、順序)ではないことに注意すること。

なお~については後述する。

 上記の図表で「.NET用」と記述されているCLRヘッダとCLRデータを組み合わせたものが、「.NETプログラムの本体」だと見なすことができる。従って.NETの世界から見ると、PEフォーマットは.NETプログラム本体を収納するための「箱(=ケース、入れ物)」でしかない。

 .NETの世界では、この箱と.NETプログラムの本体を含む物理的なファイル(もしくは、それをメモリ上にロードした後のデータ)のことを「マネージ・モジュール」(Managed Module。以降、モジュール)と呼んでいる。また、.NETプログラム本体のことは「アセンブリ」(Assembly)と呼んでいる(ちなみに.NETにおけるアセンブリは、Windows XPのサイド・バイ・サイド機能で使われる「Win32 アセンブリ 」という用語と区別するために、「.NETアセンブリ」と表記されることがある)。

 実はアセンブリは、「個々のファイル」という物理的な単位ではなく、「1つ以上のモジュール(=ファイル)の集合体」という論理的な単位で(.NETによって)とらえられる。つまり、Windows世界のアプリケーションの最小単位がモジュール(=個々のファイル)であるのに対し、.NET 世界のアプリケーションの最小単位はアセンブリ(=1つのファイルで構成されるときと複数のファイルで構成されるときがある)なのだ。

 このため.NETでは、複数のモジュール(=ファイル)を寄せ集めて1つのアセンブリを構築できる。このようなアセンブリは「マルチ・モジュール・アセンブリ」(Multi-Module Assembly。もしくは「マルチ・ファイル・アセンブリ:Multi-File Assembly」。以降、MMA)と呼ばれる。ただし、このMMAはVisual Studioのデフォルト機能では作成できないため、実際にMMAのアプリケーションを構築することはそれほど多くないようだ。

 ほとんどの.NETプログラムは、「1モジュール=1アセンブリ」の「シングル・モジュール・アセンブリ」(Single-Module Assembly。もしくは「シングル・ファイル・アセンブリ:Single-File Assembly」。以降、SMA)である。

 そこで本稿では、このSMAについてのみ解説していく。MMAについての詳細は、「MSDN:アセンブリの内容 」を参照していただきたい。

●.NETプログラム本体の構造

 次の図と表は、.NETプログラム本体(=SMA)の内部構造を示したものである。以降の解説で基礎知識として必要になるので、表内の説明もざっと一読してほしい。

.NETプログラム本体の構造
項目 説明
マニフェスト

「アセンブリ・メタデータ」とも呼ばれる。メタデータとは、

一般的には「データのためのデータ」のことである。

つまりここには、

データであるアセンブリ(=.NETプログラム本体)に関する

データ(情報)が格納されている。

ここで管理される情報には、例えば

・ランタイム・バージョン(runtime version)
 ……実行するCLRのバージョン
・メタデータ(MetaData)のアドレスとサイズ
 ……型メタデータの場所
・フラグ(flags)
 ……(不必要なネイティブ・コードが含まれない)純粋な

ILコードであるかどうか(=COMIMAGE_FLAGS_ILONLY)や

厳密名(=電子署名)があるかないか

(=COMIMAGE_FLAGS_STRONGNAMESIGNED)などの

情報
・エントリ・ポイント・トークン(entry point token)
 ……型メタデータのテーブル上にある

エントリ・ポイント(Mainメソッド)のキー番号
・リソース(Resources)のアドレスとサイズ
 ……マネージ・リソースの場所
・厳密名シグネチャ(StrongNameSignature)の

アドレスとサイズ
 ……厳密名の場所


などがある(「……」の後ろに書かれているのは、

その情報から分かること)

型メタデータ

単に「メタデータ」と呼ぶ場合はこれを指す。

ここには主に、「アセンブリ内で<定義>されている、

型(クラスなど)とそのメンバに関する情報」と

「アセンブリ内で<参照>している、型とメンバに

関する情報」という2種類の型メタデータ・テーブルが

格納されている

ILコード

「マネージ・コード」とも呼ばれる。ここには実際の

.NET用のプログラム・コードが格納されている。

このコードは、特定のCPUやプラットフォームに

依存しない抽象化された中間コードなので、

IL(Intermediate Language:中間言語)コードと

命名されている。なおILコードには、汎用フォーマットの

「CIL」(Common Intermediate Language)と

マイクロソフトが提供する拡張フォーマットの

「MSIL」(Microsoft Intermediate Language)の

2種類がある。Visual Studioや.NET Framework

付属のコンパイラで生成したプログラムはMSILである

マネージ・リソース

マネージ・リソース(Managed resources)には、

ビットマップや文字列などの(.NETにより管理された)

リソースが格納されている

 以上、.NETプログラムの構造として「箱であるモジュールの構造」と「本体であるアセンブリの構造」を説明した。それでは、本題である.NET プログラムの実行処理の流れを順に追っていくことにしよう。


.NETプログラム実行の処理の流れ(Mainメソッドまで)

 次の図は、冒頭で示したサンプル・アプリケーション(.EXEファイル)をダブルクリックして起動したときの、CLR上での処理の流れ、具体的には_CorExeMain関数を呼び出してから後の処理の流れを図示したものである。ちなみに_CorDllMain関数の場合も以下の流れと大差ない。

CLR上で.NETアプリケーションが起動する際の処理の流れ図

(Mainメソッドまで)

ディレクトリ(本稿の例では「C:\fdotnet\WindowsApplication1\bin \Release」)

の中にあるWindowsアプリケーション「WindowsApplication1.exe」を

ダブルクリックして実行したときの CLR上での処理プロセスを図示したもの。

CLRに処理が引き渡される前の、つまり_CorExeMain関数が

呼び出される前までの処理の流れについては前回の

「.NET アプリケーションが起動する仕組み 」を併せて参照するとよい。

「CLRの初期化処理」と「CLRローダーの始動」。
<アセンブリ・ローダー>による「アセンブリのロード」。
<ポリシー・マネージャ>による「厳密名の検証」。
CLRローダーによる「MainメソッドのILコード上の位置の特定」。

<クラス・ローダー>による「Mainメソッド呼び出しに

必要な型情報の収集」。

CLRローダーによる「Mainメソッドの呼び出し」。
<JITコンパイラ(型ベリファイア)>による「型の妥当性検証」。
<JITコンパイラ>による「ILコードからネイティブ・コードへの変換」。
<コード・マネージャ>による「ネイティブ・コードの実行と管理」。

 それでは、ここで図示されている一連の処理の流れを、1つずつ、もう少し詳しく説明していこう。

●「CLRの初期化処理」と「CLRローダーの始動」

 前回の最後 で説明したように、mscoree.dllの_CorExeMain関数(プログラムが.EXEファイルの場合。.DLLファイルの場合は_CorDllMain関数)が「.NET世界の玄関口となるAPI 」である(ちなみに「mscoree」は、「MicroSoft Component Object Runtime Execution Engine:マイクロソフト・コンポーネント・オブジェクト・ランタイム実行エンジン」の略である)。

(1)CLRの初期化処理

 _CorExeMain関数の内部では、まずCLRの初期化処理が行われる。具体的には、アセンブリのCLRヘッダ(マニフェスト)を解析して、そのアセンブリが対応しているCLRバージョン(=ランタイム・バージョン)を特定する。このときにもし、肝心のマニフェストがアセンブリに格納されていなければ、そこで例外が発生してプログラムの実行は終了する。

 なおCLRにはすでにいくつかのバージョンがリリースされているが、ここで特定したバージョンのCLRがローカル環境にインストールされていない場合には、別バージョンのCLRが代理として自動的に使用されることがある。例えば、特定したアセンブリのCLRバージョンが「1.0.3705」(=.NET Framework 1.0でビルドした場合)で、ローカル環境のCLRバージョンが「1.1.4322」の場合、自動的にバージョン「1.1.4322」のCLRが用いられる。

 ちなみに、アセンブリのCLRバージョンが「1.1.4322」(=新しいバージョン)で、ローカル環境のCLRバージョンが「1.0.3705」(=古いバージョン)の場合は、古いバージョンのCLRは使用されずにプログラムは終了する。つまり新しいCLRバージョンのアセンブリを古い環境で実行することは基本的にできない。

 また、アプリケーション構成ファイル(.configファイル)を使ってサイド・バイ・サイド(side-by-side)実行が指定されている場合には、その構成ファイル内で指定されたCLRバージョンが使用される。CLRのサイド・バイ・サイド実行については「TIPS:サイド・バイ・サイドによりCLRバージョンを指定するには? 」を参照してほしい。

 以上の手順によりCLRバージョンが決定したら、いよいよCLRの実行エンジン(以降、「CLRローダー」)が動作する。

 なお、実はmscoree.dllの仕事範囲は、CLRローダーを動作させる前までのつなぎでしかない。それ以降の本格的なCLRの処理は、ほかの.DLLファイル(例えばmscorwks.dllやmscorsvr.dllなど)に処理が引き継がれる。このためmscoree.dllは、「CLRの詰め木(シム)」を意味する“CLR Shim”と呼ばれることがある。

(2)CLRローダーの始動

 CLRローダーを動かすために、(CorBindToRuntimeEx関数 というWin32 APIを呼び出して)決定したバージョンのCLRローダーを、ホスト・プロセス (=現在プログラムを実行中のプロセス)に読み込む。これによってCLRローダーが動き始める。

 さらにこの際、デフォルト(=既定)のアプリケーション・ドメイン(=.NET専用のプロセス)が自動的に作成される。

●CLRローダーの<アセンブリ・ローダー>による「アセンブリのロード」

 動作し始めたCLRローダーはアセンブリ・ローダー(Assembly Loader)によって、(System.AppDomainオブジェクトのLoad メソッド という.NET APIを活用して)既定のアプリケーション・ドメインの中にアセンブリをロード(=展開)する。

 これにより、以降のアセンブリ(=プログラム本体)のすべての実行処理は、.NETの世界で進められることになる。要するにここで初めて実行環境の舞台が、Win32世界(Windowsローダー)から.NET世界(CLRローダー)に完全に遷移したといえるだろう。

●CLRローダーの<ポリシー・マネージャ>による「厳密名の検証」

 アセンブリがロードされたら、ポリシー・マネージャ(Policy Manager)が厳密名の検証処理を開始する。

 なお厳密名とは簡単にいうと、アセンブリに対して付けられる.NET特有の電子署名のことだ。厳密名はアセンブリに任意で付けることができるが、デフォルトでは付けられない(厳密名の付け方は本稿では割愛する)。厳密名を付けると、ファイルの改ざんをチェックできるので、ファイルを偽装・改ざんするタイプのウイルスを無効化するのに効き目がある。また.NETにはこの厳密名を使用したバージョン管理機能が用意されているので、厳密名を付ければその機能を活用できるというメリットもある。

 ポリシー・マネージャは、アセンブリのCLRヘッダ(マニフェスト)を解析して、厳密名の有無を確認する。そこで厳密名が設定されていなければ、厳密名の検証処理はスキップする。設定されていれば、厳密名の情報を取得して、それが正当なものかどうかを検証し、偽装・改ざんされたファイルもしくは意図と異なるファイルの実行を防止する。

 .NETではWindowsの世界に比べてアプリケーション実行時のセキュリティ・チェックが非常に厳重になっているが、ここがCLRの最初のセキュリティ・ポイントだ。

●CLRローダーによる「MainメソッドのILコード上の位置の特定」

 次にCLRローダーは、エントリ・ポイントとなるメソッドのILコード上での位置(=アドレス)を特定する。具体的には、再度CLRヘッダ(マニフェスト)を解析してエントリ・ポイントのメタデータ・トークン(つまりILコード上のMainメソッドの位置)を取得する。

 本稿のサンプル・アプリケーションでは、次のようなコードがエントリ・ポイントである。

static void Main()
{
……中略……
}

 このようにC#やVisual Basic .NETでは、基本的にMainメソッドがエントリ・ポイントのメソッドとなる。

●CLRローダーの<クラス・ローダー>による「Mainメソッド呼び出しに必要な型情報の収集」

 エントリ・ポイントであるMainメソッドの位置が判明したら、次にクラス・ローダー(Class Loader)がそのMainメソッドを公開している型(例えば本稿では「WindowsApplication1名前空間のForm1クラス」)を取得する。そして、動的に割り当てたメモリ上に、その型の「内部データ構造」(Internal Data Structure)をロードする。

 クラス・ローダーは、ロードしたクラス情報のメソッド1つ1つに対して、「スタブ」を設定する。スタブとは「ほかのプログラムを呼び出すための仲介となるプログラム」のことだが、ここではそれぞれのメソッドを呼び出すと、(メソッドそのものではなく)メソッドに設定されたスタブを仲介して(次で説明する)「JITコンパイラ」(JIT Compiler。“JITter”ともいう)が呼び出される仕組みになっている。

●CLRローダーによる「Mainメソッドの呼び出し」

 ここでCLRローダーは実際にMainメソッドを呼び出そうとする。そこで先ほどのスタブを仲介して、JITコンパイラが働きだす。具体的には、(次で説明する)「型の妥当性検証」と「変換処理」が実行され、その後のでやっと実際にMainメソッドが実行されることになる。

●CLRローダーの<JITコンパイラ(型ベリファイア)>による「型の妥当性検証」

 JITコンパイラが呼び出されると、型ベリファイア(Type Verifier)によって、次の検証処理を実行する。

  • 呼び出そうとしている型やメソッド(のメタデータ情報)が正確・正当なものなのかどうかを調べる

  • 呼び出そうとしている型やメソッドのシグネチャが正しいかどうかを調べる

 これらの検証で失敗した場合、例外が発生してプログラムの実行は終了する。これが「タイプ・セーフ」(Type Safe)と呼ばれるセキュリティ・ポイントである。

●CLRローダーの<JITコンパイラ>による「ILコードからネイティブ・コードへの変換」

 型の検証処理をパスすると、JIT(Just-In-Time)コンパイラは本来の仕事を開始する。ここがCLRのキモの部分だ。

 つまりJITコンパイラは、その場で(=ジャスト・イン・タイムで)、Mainメソッドの「ILコード」を「マシン環境に最適化したCPUネイティブの命令」(基本的には「x86機械語コード」。以降、ネイティブ・コード)にコンパイルして変換する。そして、変換したネイティブ・コードを、動的に割り当てたメモリ・ブロックに格納する(なおこのメモリ上のネイティブ・コードはアプリケーションを終了すると同時に自動的に破棄される)。

 続いてJITコンパイラは、で用意した型の内部データ構造のMainメソッドのスタブを、ここでコンパイルされたネイティブ・コードのMainメソッドのアドレスに置き換える。これにより、次にMainメソッドを呼び出すときには、(スタブからJITコンパイラが呼ばれるわけではなく)直接にネイティブ・コードを呼び出せる(※ただしMainメソッドに限っては基本的に2度呼ぶことはないだろうが……)。

 このように、JITコンパイラによって検証とコンパイルが済んだメソッドは、それが2回以上呼ばれる際にはJITコンパイラを呼ばないようにして、処理を最適化している。

●CLRローダーの<コード・マネージャ>による「ネイティブ・コードの実行と管理」

 Mainメソッドのコードは、CPUが読み取って処理できるネイティブ・コードへと変換されているので、後はそのコードへジャンプすればプログラムが動き始める。これ以降のコード実行は、コード・マネージャ(Code Manager)により管理・制御される。これにより、以下のようなサービスが提供される。

  • ガーベジ・コレクション(Garbage Collection):未使用のオブジェクトを自動的に破棄してメモリを解放するサービス

  • 例外管理(Exception Manager):例外処理メカニズムによりアプリケーション・エラーをすべて管理できるサービス

  • セキュリティ(Security Permission Enforcement):コード・アクセス・セキュリティ(code-access security)によりメソッドなどのコードの実行権限を細かく制御できるサービス

●Mainメソッド以降のCLRローダーの処理の流れについて

 Mainメソッド以降の処理の流れでは、メソッドが呼び出されるたびに、その事前処理として、クラス・ローダーによって、メソッド内のすべての型の内部データ構造がメモリ上にロードされ、メソッドにスタブが設定される。当然ながら、すでにメモリ上にロードされている型はロードされない。

 本稿のサンプル・アプリケーションの例でいえば、MainメソッドでApplicationクラスとForm1クラスの型を使用しているので、 Mainメソッドを呼び出すときにはクラス・ローダーによってこれらの型情報が検出されて、メモリ上にロードされることになる。ただしForm1の型情報は、Mainメソッドをロードするときにすでにロードされているので実際にはここではロードされない。

static void Main()
{
Application.Run(new Form1());
}

 なお型情報の検出は、アセンブリの型メタデータを参照して行われる。その型が外部アセンブリのものである場合には、アプリケーション構成ファイル(.configファイル)のコードベース(<codeBase>要素)や、GAC(Global Assembly Cache:グローバル・アセンブリ・キャッシュ)、アプリケーション・パスなどに基づいて外部アセンブリの場所を特定し、上記の「アセンブリのロード」「厳密名の検証」と同じような処理が実行される。なお、GACのアセンブリを利用する場合は、厳密名の検証処理はスキップされる。

 型がメモリにロードされたら、実際にメソッドが呼ばれる。この後は~と同じような処理の流れになる。違うのは、Mainメソッドがほかのメソッドになることである。

■

 以上3回にわたり、.NETアプリケーションが動作する仕組みを、コンピュータの世界、Windows(Win32)の世界、.NETの世界という観点から解説した。これで本連載は完結だ。最後までお付き合いいただいたことに感謝の意を表したい。End of 
Article

第2回 NETアプリケーションが起動する仕組み

 前回 は、パソコン上でWindows OSやアプリケーション・ソフトウェアなどのプログラムがどのようにして動作しているのかを、PCというハードウェア環境の観点で説明した。今回は、アプリケーションが起動する仕組みについて、Windows OSというソフトウェア実行環境の視点で解説する。

 ただし、今回説明するのは、.NETアプリケーション(以降、.NETアプリ)が.NET Frameworkの実行環境(「CLR:Common Language Runtime 」と呼ばれる)の上で動作を開始するまでの処理過程の話である。実際に.NETアプリケーションがCLRにより実行される仕組みついては次回で解説する予定だ。

 前置きはこれくらいにして、さっそく「.NETアプリケーションが起動する仕組み(Windowsローダーの働き)」について説明していこう。なお本稿で提示されている図版などは、説明を簡略化するために、すべての詳細を正確に明記してはいないことをあらかじめお断りしておく。

Windows OSという視点から見た場合のプログラム実行の仕組み

 まずはWindows OSを利用するユーザーの視点で、Windows OS上でアプリケーションが起動する際の処理プロセスについて考えてみよう。

●Windows OS上でアプリケーションが起動する際の処理プロセス

 ここで前提知識として、次のことを知っておいてほしい。

 それは、.NETアプリケーションであろうと、Win32アプリケーションであろうと、それらのプログラムがWindows OSから最初に起動される処理過程は、基本的に同じであるということだ(なお、「.NETアプリケーション」と「Win32アプリケーション」の違いについては、前回 の説明を参照してほしい)。

 従って本稿では、.NETアプリケーションの起動例を取り上げることで、Windows OS上でのアプリケーション起動の仕組みを説明することにした。例に用いる.NETアプリケーションとして、本稿では「WindowsApplication1.exe」というシンプルなWindowsアプリケーションを用意した。

 次の画面は、そのアプリケーションをダブルクリックして起動したときの、処理の流れを図示したものである。

Windows OS上でアプリケーションが起動する際の処理の流れ図
ディレクトリ(この例では「C:\fdotnet\WindowsApplication1\bin \Release」)の
中にあるWindowsアプリケーション「WindowsApplication1.exe」を
ダブルクリックして実行したときの処理プロセスを図示したもの。
なお、~ の処理プロセスで示されている、CPUおよびメイン・メモリで
の処理の流れについては、前回の
「Windows 上でプログラムが実行されるまでの処理の流れ 」を併せて
参照するとよい。
  ユーザーが「WindowsApplication1.exe」という<ファイル>を
ダブルクリックする。
  Windows OSは、をきっかけとして、クリックされたファイルである「WindowsApplication1.exe」がプログラムかどうかを判定する。
  Windows OSは、の作業によりファイルがプログラムと
判定されると、そのプログラム・データ(=プログラム・コード)を
メイン・メモリ上にロードしたうえで、<Win32アプリケーション>の
プログラムとして実行する。
  メイン・メモリ上にロードされた「WindowsApplication1.exe」の
プログラム・コードを、 CPUが1つずつ処理していく。これにより、
.NETの実行環境「CLR」が構築されて、その環境上で
アプリケーションが実行される。そして最終的に、 Windows OSの
デスクトップ画面上に、[Form1]というタイトルで
アプリケーション・ウィンドウが表示される。

 それでは、ここで図示されている一連の処理の流れを、1つずつ、もう少し詳しく説明していこう。

●ユーザーがファイル「WindowsApplication1.exe」をダブルクリックする

 この一連の処理プロセスの中で特に注目すべきは、これまで「プログラム」や「アプリケーション」と呼んでいたものの正体が、実は単なる<ファイル>に過ぎなかったということである。なるほど、ファイルの種類には、例えば「テキスト・ファイル」「Word文書ファイル」などなど、いろいろなものがあるが、プログラムもそれらのファイルと本質的には何ら変わらないわけである。

 しかしよく考えてみると、Windows OSにとってこれが1つの課題・障壁となるのではないだろうか。もしそれらがすべて「ファイル」という同じものであるのならば、Windows OSはそのファイルが一体どのようなものか、つまり「ファイルの種類」(以降、ファイル・タイプ)を認識しなければならないはずだ。このファイル・タイプが分からなければ、そのファイルに対する適切な操作方法(例えば「Windows OS上でプログラムとして直接実行する(Execute)」のか、「アプリケーションでそのファイルを開く(Open)」のかなど)も分からないはずだからである。

 当然ながらWindows OSはこの課題をクリアしている。例えば、ユーザーが「memo.txt」というテキスト・ファイルをダブルクリックすると、Windows OSは、そのファイルのファイル・タイプを正確に判定して(この例では、「テキスト・ファイルである」と判定される)、ユーザーが要求するアクション(この例では、「アプリケーションでそのファイルを開くこと」)を適切に開始する。これによりWindows OSは、ユーザーが意図するアプリケーションを、正しく起動できるわけである(この例では、「メモ帳(notepad.exe)」などのテキスト・エディタ・アプリケーション で実際に開かれる)。

●Windows OSが「WindowsApplication1.exe」がプログラムかどうかを判定する

 このように、OSによるプログラムの起動では、「ファイル・タイプの判定」作業が肝心である。ファイル・タイプの判定方法は、OSごとに異なるが*1、Windows OSでは、「拡張子 の名前」(この例では「.exe」)によって行われている(これは「拡張子とファイル・タイプの関連付け」と呼ばれるWindows OSが持つ機能の上に成り立っている)。つまり、先ほどの課題に対するWindows OSの場合の解決方法は、「拡張子」なのである。

*1 ちなみにMac OSは、基本的にファイル・タイプを「拡張子」では判定しない。その代わりに、Mac OS専用のファイル・フォーマット(「Mac OS拡張フォーマット」または「HFS Plusフォーマット」と呼ばれる)のヘッダ部分(=多くの場合、ファイルの先頭部分に配置されるファイル自体の構成内容に関する情報を格納するための領域)にあるファイル情報(「Finder情報」と呼ばれる)を用いて、ファイル・タイプを判別している。ただし最新のMac OS Xなどでは、Windows OSと同じように(Finder情報を使わずに)「拡張子」で判定されるケースも多いらしい。

 Windows OSでは、ファイルの拡張子が「.exe」である場合、そのファイルのファイル・タイプは「実行可能ファイル(Executable File) だ」と識別される。つまり、「Windows OS上で実行可能なプログラムである」と判定されるわけだ。

 従って、本稿の例の「WindowsApplication1.exe」というファイルには、「.exe」という拡張子が付けられているために、実行可能ファイルとして取り扱われて、実際にプログラムとして実行が開始されることになる。

●Windows OSは「WindowsApplication1.exe」をプログラムとして実行する

 プログラムの実行開始を受け持つWindows OSの機能は、「Windowsローダー(Windows Loader)」と呼ばれている。Windowsローダーは、「実行可能ファイル」と判定されたファイルを、「Microsoft Portable Executeフォーマット」*2(以降、「PEフォーマット」)と呼ばれるプログラム実行用(主にWin32アプリケーション)のファイル・フォーマット(=ファイル形式)で作成されたファイルとして取り扱う。

 つまり、すべての実行可能ファイル(.exeファイル)は、PEフォーマットで作成されている必要があるわけだ。これについても、Win32アプリケーションであろうが、.NETアプリケーションであろうが、等しく同じ条件である。すなわち、.NETアプリケーションといえども、Windows OSからは「実行プログラムの1つである」と見えているのだ*3。

*2 PEフォーマットは、Win32アプリケーションの基本フォーマットで、.NETアプリケーションやWin64アプリケーションでも用いられるフォーマットである。
*3 厳密にいえば、Windows XPやWindows Server 2003以降のOSは、.NETアプリケーションの存在を知っており、.NETアプリケーションを「Win32アプリケーション」ではなく、きちんと「.NETアプリケーション」として認識している(詳細後述)。

 実行可能ファイルが正しくPEフォーマットで作成されていれば、Windowsローダーは、そのファイルを<Win32アプリケーション>のプログラムとして取り扱い始め、実際にプログラムとして起動する。その際の処理過程は次の図のとおりだ。

Windowsローダーによるアプリケーションの起動プロセス
Windowsローダーは、PEフォーマットで作成された実行可能ファイルを、
<Win32アプリケーション>のプログラムとして起動する。
具体的な手順の内容は以下の記述を参照。

Windowsローダーは、PEフォーマットに基づいて、実行可能ファイルの先頭部分に格納されている情報(以降、「PEヘッダ」)を解析する

Windowsローダーは、解析結果に基づき、実行に必要なプログラム・コードをメイン・メモリ上にロードする

Windowsローダーは、解析結果に基づき、プログラムの実行開始位置(Entry Point。以降、「エントリ・ポイント」)にアクセスし、そこに記述された処理命令をCPUに実行させる

CPUが、そのエントリ・ポイントから順番に1つずつ処理命令を実行していくことで、プログラムが実行開始される

 これらの一連のWindowsローダーの処理がすべて完了して、初めてプログラムが現実に実行される(本稿の例でいえば、[Form1]という Windowsアプリケーションのウィンドウが表示される)わけである。

●Windows OSのデスクトップ画面上に、[Form1]というタイトルでアプリケーション・ウィンドウが表示される

 ここまでが、.NETアプリケーションが<Win32アプリケーション>として起動するまでの流れである。そしての処理段階からは、.NET Frameworkの実行環境「CLR」へと、その実行の舞台が移される。つまりプログラムが、<Win32アプリケーション>という存在から、<.NETアプリケーション>という存在へと変ぼうするのだ。このの処理段階以降の話については、次回詳しく説明するので、それまでお待ちいただきたい。

 以上が理解できたら、の処理内容をもっと具体的に細かく見ていくことにしよう。次のページでは、 Windowsローダーが処理する手順で、プログラムの内部を1つずつ追っていくことにする。


Windowsローダーがアプリケーションを起動する処理プロセス

 それではWindowsローダーの処理を1つずつ順に追っていくことにしよう。

●Windowsローダーによる「PEヘッダ」の解析

 Windowsローダーは最初に、PEフォーマットで作成された実行可能ファイルのヘッダ部分、つまり「PEヘッダ」を調べて、どのような起動手順を踏めばよいかを理解する。従ってまずは、このPEヘッダの中身を調べてみよう。

 私たち人間がPEフォーマットの内容を参照するためのツールとして、「dumpbin.exe」というコマンドライン・ツールがある。本稿ではこれを用いてPEヘッダの内容を解析する。なおdumpbin.exeは、マイクロソフトが提供する統合開発環境のVisual Studio .NETやVisual C++ .NETなどに付属するツールの1つなので、実際に読者諸氏がこのdumpbin.exeを使うには、これらの開発環境のいずれかがコンピュータ上にあらかじめインストールされている必要がある。

 PEヘッダ情報を確認するには、dumpbin.exeのパラメータ(=引数)に、実行可能ファイルを指定し(本稿の例では「WindowsApplication1.exe」)、さらに「/header」オプションを付けて実行すればよい。これを、実際に実行しているのが次の画面である。

実行可能ファイル「WindowsApplication1.exe」のPEヘッダの内容
「WindowsApplication1.exe」という実行可能ファイルのPEヘッダを表示した
ところ。 Visual Studio .NET 2003がインストールされた環境で、
[スタート]メニューの[すべてのプログラム]-
[Microsoft Visual Studio .NET 2003]-[Visual Studio .NET ツール]-
[Visual Studio .NET 2003 コマンド プロンプト]を選択すれば、
コマンド・プロンプトが表示される。このコマンド・プロンプトには、
Visual Studio .NET関連ツールにパスが通っているため、
開発関連のツールにフル・パスを指定しなくても、
ツール名だけでアクセス(=利用)できるようになる。
  コマンド・プロンプト内で「cd」コマンドを使って、.NETアプリケーションの
あるディレクトリまで移動する。この例では
「cd C:\fdotnet\WindowsApplication1\bin\Release」というコマンドを
入力して[Enter]キーを押し下げてそのコマンドを実行している。
  dumpbin.exeのパラメータに実行可能ファイルを指定して、
「/headers」オプションを付けて実行する。この例では、
「dumpbin.exe WindowsApplication1.exe /headers」という
コマンドを入力して実行している。
  コマンド・プロンプト上に出力されたPEヘッダの内容。
すべての出力内容は上記の「全出力内容を表示する」のリンクから参照
できる。

 上の画面のPEヘッダ情報で表示されている各数値は、基本的に16進数 *4で記述されている。なお以降の説明では、16進数の値には、基本的に「0x」というプレフィックス(=頭文字)を付けて表現する(例えば、「0x400000」)。

*4 以降の説明を理解するには、16進数を読める必要がある。16進数の数え方を学ぶには、「@IT自分戦略研究所」の「コンピュータの基本、2進数を理解しよう 」がお勧めである。

 次に、このPEヘッダの情報から解析・確認できる次の2点の内容を追いかけてみよう。

  1. のメイン・メモリ上にロードされたプログラム・コードの先頭位置(と範囲)
  2. のエントリ・ポイント(=実行開始位置)に記述されているプログラム・コード

●Windowsローダーによるメイン・メモリ上へのプログラム・データのロード

 まず1の方は、PEヘッダの「OPTIONAL HEADER VALUES」というセクションの「image base」という項目を見れば分かる。本稿の例では、この項目は次のようになっている。

OPTIONAL HEADER VALUES
  …中略……
  400000 image base (00400000 to 00407FFF)

 「image base」には、メイン・メモリ上にロードされるときのプログラム・コードの先頭位置のアドレス(=メモリ上の番地*5)が示される。従って本稿の例では、メモリ上のアドレス「0x400000番地」を起点として(「0x407FFF番地」のアドレスまでの範囲に)、プログラム・コードがロードされるということが、この「image base」の情報から読み取れる。

*5 ただしこれはプログラマから見えるメモリ空間の「仮想アドレス」で、実際の「物理アドレス」ではない。
 
メイン・メモリ上へのロードされるときのプログラム・コードの先頭位置
(とその範囲)

●Windowsローダーによるエントリ・ポイントへのアクセス

 次に2の方は、PEヘッダの「OPTIONAL HEADER VALUES」セクションの「entry point」項目に記されている。本稿の例では、次のようになっている。

OPTIONAL HEADER VALUES
  …中略……
  3A2E entry point (00403A2E)

 「entry point」項目には、プログラム実行の開始地点(=プログラム実行のエントリ・ポイント)のメモリ上のアドレスが値として表示される(この例では「0x3A2E」)。ただしこのアドレス値は、先ほどの「image base」のアドレスを基準にした相対的なアドレス値である*6。従って、本稿の例でいえば、このentry pointの「0x3A2E」という値と、image baseの「0x400000」という値を足し合わせた、「0x403A2E番地」が真のエントリ・ポイントのアドレスということになる。

*6 このような「相対的な仮想アドレス」は、「RVA:Relative Virtual Address」と呼ばれる。
 
プログラム・コードの実行開始地点「エントリ・ポイント」の位置

●エントリ・ポイントに記述されているプログラム・コードの内容

 Windows OSは、プログラムを起動開始するために、エントリ・ポイントのアドレス(本稿の例では、「0x403A2E」)に記載されているプログラム・コードを呼び出す。

 それでは、エントリ・ポイントのアドレスには、一体どのようなプログラム・コードが書かれているのだろうか? もちろんこれは、実行するアプリケーションによって、さまざまに異なるわけだが、.NETアプリケーションでは、このエントリ・ポイントの部分のプログラム・コードが(基本的に)固定的な内容となっている。実際にはどのような内容になっているのか、そのプログラム・コードの中身を調べてみよう。

 メモリ上にロードされるときのプログラム・コードの内容を調べるには、先ほども利用したdumpbin.exeに、実行可能ファイルを指定し(本稿の例では「WindowsApplication1.exe」)、さらに「/rawdata」オプションを付けて実行すればよい。これを実際に実行しているのが次の画面である。

「WindowsApplication1.exe」のエントリ・ポイント部分のプログラム・コードの内容
この例では、「dumpbin.exe WindowsApplication1.exe /rawdata:1,16 | more」
というコマンドを入力・実行して、メモリ上にロードされるときの
プログラム・コードの内容を参照している(実はこれは、単にファイル内容を
ダンプ(=出力)しているだけで、左端にあるアドレス表示がメモリ上に
ロードされるときのアドレスとなっているだけだ。実際のメモリ上の内容を
参照しているわけではないので注意すること)。なお、「/rawdata」オプション
の後に続く、「:1」は1byte (=8bits )ずつのバイト列を出力するという指定を
意味する。これによりexeファイルの内容が16進数2けた(「00」~「FF」)の
並びにより表示される。また「,16」は、1行の中に、16進数2けたの値が
何個並ぶのかを指定している(この例では、「16」列を指定しているので、
「00」「FF」といった数値が16個、横に並ぶことになる)。
最後に「 | more」というコマンドが付加されているが、これはページ単位で
出力を表示させるためのものである(これを付けないと、全内容が一気に
出力されてしまい、目的のアドレス部分を確認できない)。
  「WindowsApplication1.exe」のエントリ・ポイントのアドレスである
「0x403A2E」のプログラム・コードを探し出す。
  エントリ・ポイント以降にあるプログラム・コードが
すべて読み取られる。この例では、「FF 25 00 20 40 00」が
読み取れる。なお、どの.NETアプリケーション(.exeファイル)でも
この内容は基本的に同じである。ちなみに、.NETで作られた
DLL ファイルの場合は、エントリ・ポイントのアドレスも、
プログラム・コードの内容も、これとは若干異なる。

 上の画面の内容から、エントリ・ポイント部分のプログラム・コードは、次のような6bytesのコードで成り立っていることが分かる。

FF 25 00 20 40 00

 このような数値の並び(「機械語 」と呼ばれる)を見ただけで、その意味まで読み取ることは難しい(もちろんコンピュータにとっては簡単だが……)。そこで、このようなプログラム・コードを人間が容易に記述したり、読んだりするために、機械語の「数値の並び」が持つ意味(例えば、CPUの処理命令など)に対して「名前」を割り当てた「アセンブリ言語 」というものが存在する。

 ということで、上記の機械語のプログラム・コードを、アセンブリ言語のプログラム・コードへ置き換えてみよう。そうすれば、このプログラム・コードの意味がはっきりするはずだ。

 まず、先頭の「FF 25」という機械語は、アセンブリ言語の「JMP <32bitsアドレス>」という構文に置き換えられる。この構文に従えば後半に32bitsアドレス(=4bytes)が続くはずなので、残りの数値である「00 20 40 00」は、そのような4bytesからなる1つのアドレス値に置き換えなければならない。ここで注意が必要である。x86系のCPUアーキテクチャでは、メモリへのデータ格納方法として、「リトルエンディアン 」と呼ばれる手法が用いられている。このリトルエンディアン方式では、最下位のバイト(byte)から1つずつ上にさかのぼりながらデータを格納する。つまり、「00 20 40 00」という数値の並びは、最下位のバイトから順に読み取って、「00 40 20 00」→「0x00402000」というアドレスになる。

 以上の置き換え内容をまとめると、6bytesのコードは次のようなアセンブリ言語の命令文と同等である。

JMP 0x00402000

 これは「0x00402000番地のアドレスへジャンプ(=移動)せよ」という処理命令を意味する。よって、ジャンプ先のアドレス「0x00402000」にあるプログラム・コードが次に実行されることになる。

 それでは、このジャンプ先のアドレスには、どのようなプログラム・コードが存在するのだろうか? 結論からいうと、ジャンプ先にあるプログラム・コードは、すでに.NETの実行環境の領域に入る。

 これが意味することは、.NETアプリケーションの起動処理においては、「Win32アプリケーションとして実行されるプログラム・コード(以降、「アンマネージ・コード 」)は、たった6bytesしかない」ということだ。それ以外のすべてのプログラム・コードは、.NETアプリケーションとして実行されるプログラム・コード(以降、「マネージ・コード 」)である。

 しかも、Windows XP/Windows Server 2003以降では、Windows OS自体が.NETアプリケーションの存在と起動手順を熟知しているため、いま述べた6bytesのプログラム・コードの実行はスキップして、直接、.NETの領域部分にジャンプして、.NETアプリケーションのプログラム・コードから実行を開始する。つまりこれらのOSでは、完全に.NET環境のみで実行されるのである。

●処理命令の順次実行によるプログラムの実行開始

 最後に、先ほど示したジャンプ先のアドレス「0x00402000」に、何があるのかを確かめて今回は終わりにしよう。

 これを説明する前に、次のことを確認していただきたい。

 それは、Win32アプリケーションでは、ファイル内のプログラム・コードだけでなく、別ファイルの、具体的にはDLLファイル (.dll ファイル。ライブラリ)のプログラム・コードも利用することができる、ということだ。

 このような外部のDLLファイルの使用に関する情報(以降、「インポート情報」)は、あらかじめPEフォーマットのファイル内にデータとして格納されている。

 そこで、再度コマンドライン・ツール「dumpbin.exe」に登場してもらい、このインポート情報を参照してみよう。これを行うには、 dumpbin.exeのパラメータに.NETアプリケーションを指定し(本稿の例では「WindowsApplication1.exe」)、さらに「/imports」オプションを付けて実行する。これを実際に実行しているのが、次の画面である。

実行可能ファイル「WindowsApplication1.exe」のインポート情報の内容
この画面では、「dumpbin.exe WindowsApplication1.exe /imports」という
コマンドを入力して、実行可能ファイル「WindowsApplication1.exe」の
インポート情報の内容を参照している。なお「/imports」オプションは、
実行可能ファイルが使用する(=インポートする)DLLファイルの一覧情報
(=インポート情報)を表示するためのものである。
  この情報から、「WindowsApplication1.exe」には「mscoree.dll」
という外部の DLLファイルを利用している(=インポートしている)
ことが読み取れる。そして、そのインポート情報
(Import Address Table)が格納されているメモリ上の
先頭アドレスが「0x402000」であることが分かる。
  この情報から、「mscoree.dll」のインポート情報のインデックスが
「0番」のところに、「_CorExeMain」という関数があるのが分かる。

 この画面を見ると、アドレス「0x00402000」に、「mscoree.dll」というDLLファイルのインポート情報が存在し、さらにそのインポート情報のインデックス「0番」の場所に、「_CorExeMain」という関数が登録されているのが分かる。つまり、先ほどのプログラムのエントリ・ポイントにあったプログラム・コードが実行されると、このインポート情報のアドレス「0x00402000」まで飛んできて、mscoree.dll ファイルにある_CorExeMain関数を呼び出すというわけである(なお、この呼び出し先である_CorExeMain関数のアドレスは、実行時に動的に決定される)。

 実はこの「mscoree.dll」は、<.NET Framework>によりインストールされるDLLファイルの1つで、.NETが動作する環境ならば、Windows OSのシステム・ディレクトリ「%Systemroot% \System32」に配置されているはずだ。

 従って、.NET Frameworkが提供するmscoree.dllの中に含まれる、この_CorExeMain関数こそが、「.NETアプリケーションが.NETとして動作し始める際の最初のエントリ・ポイント」なのである。ちなみに、DLLファイル(.dllファイル)の場合の.NETプログラムとしてのエントリ・ポイントは、mscoree.dllファイルの_CorDllMain関数である(DLLファイルの実行手順については本連載では割愛するが、本稿の手順により調べていけば、それを確かめることができるだろう)。

■

 今回はWindows OS上でアプリケーションが起動する仕組みを順を追って解説した。しかし、今回紹介した仕組みは、.NETアプリケーションが.NET Frameworkの実行環境である「CLR」上で動作し始める前の処理段階でしかない。_CorExeMain関数が呼び出された後から、初めて実際に<.NETアプリケーション>として動作し始めるわけだが、その仕組みついては次回、解説する。End of 
Article

第1回 .NETアプリケーションを動かす土台

 本連載では、新たに.NETで開発を始めようとしている新人プログラマを対象に、.NETアプリケーションが動作する仕組み、つまり「.NETの動作原理」について解説する。

 もちろん、有能なプログラマや優秀なアーキテクトを目指している読者は、これまでも普通の人よりはコンピュータを使ってきただろう。だから、メール・ソフトやブラウザなどのアプリケーションはある程度思いのままに操れるし、Windows OSのトラブルにも何とか対処できる自信があるに違いない。

 しかしこれからは、そのような「ソフトウェアがうまく使える能力」を磨くだけでは済まなくなる。これからは、「普通の人に役立つ、面白くて、便利で、使い勝手の良いソフトウェアを開発するための能力」を伸ばしていかなくてはならないはずだ。

 「そのために、まずは.NET開発を極めたい。それにはプログラムが実行される仕組み、とりわけ.NETの動作原理の基礎はきちんと学んでおきたい。」

 本連載は、そういう熱意のある新人開発者のための記事である。

 本連載は、次のような構成で進める予定である。

第1回 .NETアプリケーションを動かす土台(パソコンとOSの動作原理)
第2回 .NETアプリケーションが起動する仕組み(Windowsローダーの働き)
第3回 .NETアプリケーションが実行される仕組み(CLRの動作原理)

 今回は、「パソコンとOSの動作原理」について解説する。それぞれの項目を解説する際には、あまり難しくなりすぎたり、細かくなりすぎたりしないように気を付け、なおかつ基本はきちんと押さえた記事にしていきたい。どうか、連載終了(恐らく3回)までお付き合いいただければ幸いである。

Windowsプログラミングの歴史的変化

●Windows OSの普及と進化

 1995年11月23日。この日、Microsoft Windows 95(日本語版)の発売が開始された。それから今日まで、約10年の歳月が流れたことになる。その間、Windows というオペレーティング・システム(OS) を利用するコンピューティング環境は、個人、企業、産業を問わずに、急速にかつ幅広く普及した。マイクロソフトは、この10年という年月の流れの中で、Windows OSのバージョンアップを繰り返しながら、機能やユーザビリティ (=使い勝手)、パフォーマンス(=性能)、セキュリティ(=安全性)などを大きく向上させてきた。

【コラム】Windows OSの歴史

 次の表にあるように、これまでマイクロソフトは、新しいバージョンのWindows OSを次々と市場に投入してきた。1995年より以前のバージョンについては、「@IT:Insider's Computer Dictionary [Windows] 」を参照してほしい。なお、本表にある「クライアントOS」とは、一般的なユーザーが利用することを目的としたOSを意味し、「サーバOS」とはWebサイト構築やファイル共有などの目的で複数のクライアントからアクセスされることを目的としたOSを意味している。

OS名 発売日 説明
Windows 95 1995年11月23日 クライアントOS。MS-DOSなしで利用できる32bit対応OSだが、内部には旧来使われていた16bit カーネルが残されており、システムの安定性に弱いところがある(この系統のOSを、以降では「Windows 9x系」と表記)
Windows NT Server 4.0 1996年12月10日 サーバOS。完全な32bit対応のOSとして、Windows 9x系とは別に新規開発されたWindows OSで、セキュリティやシステムの安定性に優れている(この系統のOSを、以降では「Windows NT系」と表記)
Windows NT Workstation 4.0 1996年12月10日 上記Windows NT 4.0のクライアントOS。OSとしての基本機能や信頼性などはサーバ版と共通
Windows 98 1998年7月25日 Windows 9x系のクライアントOS。Windows 95のメジャー・バージョン・アップ
Windows 98 SE 1999年9月10日 Windows 9x系のクライアントOS。Windows 98のマイナー・バージョン・アップ
Windows 2000 Server 2000年2月18日 Windows NT系のサーバOS。Windows NT Server 4.0のメジャー・バージョン・アップ
Windows 2000 Professional 2000年2月18日 Windows NT系のクライアントOS。Windows NT Workstation 4.0のメジャー・バージョン・アップ
Windows Me 2000年9月23日 Windows 9x系最後のクライアントOS。Windows 98のメジャー・バージョン・アップ
Windows XP 2001年11月16日 Windows NT系のクライアントOS。Windows 2000 Professionalのメジャー・バージョン・アップ
Windows Server 2003 2003年6月25日 Windows NT系のサーバOS。Windows 2000 Serverのメジャー・バージョン・アップ

 もちろん進化したのはOSだけではない。そのOS上で動作する個々のアプリケーション・ソフトウェア(以降、アプリケーション)も同様に進化してきた。例えば、インターネット上のWebサイトを参照するための「IE(Internet Explorer)」などは、これまでにもたびたびバージョン・アップしてきたのだ。

●Windowsにおけるプログラミング環境の変遷

 Windows 95が登場したころのアプリケーションは、Windows OSが提供するAPI (Application Programming Interface:アプリケーションをOS上で動作させるためのインターフェイス。APIは、例えば「ファイルを開く」といった1つの機能をアプリケーションに対して提供するもの)である「Win32 API」を用いて開発することが一般的だった*1。

 しかしWin32 APIは、Windows OSと密接に結び付いているため*2、Windows OSの影響を直接的に受けてしまうという問題があった。Windows OSのバージョン・アップの歴史とは、簡単にいえばWindows OSの機能拡張の歴史でもあるのだが、こうして拡張された機能をアプリケーションが利用できるようにするには、既存のAPIを拡張するか、新しいAPIを追加するしか方法がない。このように増築を繰り返しながら、API全体としての一貫性や統一性を維持することは不可能だ。このためアプリケーション開発者は、OSのバージョンごと(例えば、Windows 98とWindows Server 2003など)でのAPIの違いに注意したり、一貫性のないAPI群を複雑な手順で呼び出したりしなければならず、開発が非常に面倒だったのだ。

*1 そのように開発されたアプリケーションは「Win32アプリケーション」と呼ばれる。
*2 Win32APIは、このようなOSの機能に密接に結びついた原始的なシステム・インターフェイスであることから「低水準のAPI」と呼ばれる。逆にインターフェイスを高度に抽象化したもの(例えば、一連の低水準APIの呼び出しを1つのAPIにまとめたものなど)は、「高水準のAPI」と呼ばれる。

 そこで、Windows OSの異なるバージョン間の違い(例えば、Windows 95とWindows XPの違い)をあまり意識する必要がなく、しかもよりシンプルな開発が可能な(つまり、より手軽で簡単にAPIの呼び出しが行える)アプリケーション開発手法が、これまでにいくつも出てきた。

 例えば、(旧来の)「Visual Basic」というプログラミング・ツールでは、プログラミング初心者がなじみやすいWindowsアプリケーションの画面デザインを中心にした開発が行えるプログラミング手法が提供された。また、「Visual C++」という開発ツールでは、大規模なアプリケーション開発に適した(より効率的な開発が可能となる)「オブジェクト指向」*3という考え方に基づく「MFC(Microsoft Foundation Class)」というクラス・ライブラリ(=オブジェクト指向の「クラス」という言語構文機能によって、利用目的ごとにAPIを寄せ集めてまとめたもの。例えば、ファイル操作を目的として「ファイルを開く」「ファイルにデータを書き込む」「ファイルのデータを読み込む」「ファイルを閉じる」という複数の APIを寄せ集めたクラスなど。このようなクラスをたくさん作成してまとめたものがクラス・ライブラリである)が提供されている。

*3 オブジェクト指向については「オブジェクト指向プログラミング超入門 」を参照してほしい。

 このようなプログラミング環境の発展のおかげで、Windowsアプリケーションの開発生産性は飛躍的に高まった(つまり、より早く、より多くの機能を簡単に開発できるようになった)。

●現在のWindowsプログラミングの主流「.NET開発」

 そのような開発スタイルの進化は、最終的に「.NET Framework 」を生み出した。これが現在のWindowsプログラミング手法の主流となっている。

 .NET Frameworkとは、「現時点で最も高水準かつ高機能なAPI」(=クラス・ライブラリ)を利用できる開発スタイルだと考えればよい*4。.NET Frameworkを使えば、高機能なアプリケーションを素早く簡単に開発できるだけでなく、開発できるアプリケーションの種類も、Windowsアプリケーションから、インターネット上のWebサイトとなるWebアプリケーション、PDA(Personal Digital Assistants) などの携帯端末までと幅広い*5。

*4 詳しくは「初めてのMicrosoft .NET 」を参照してほしい。
*5 .NET Frameworkを用いたアプリケーションの開発方法については、「簡単!Visual Studio .NET入門 」や「オブジェクト指向プログラミング超入門 」を参照してほしい。本稿では、そこで開発された.NET Frameworkを用いたアプリケーションが、どのようにして実行されるのかに焦点を当てて解説する。

 本連載では、「このような.NET Frameworkを用いたアプリケーション(以降、.NETアプリケーション)が、どのようにして起動し、どのようにして実行されるのか?」という.NETの仕組み・動作原理について解説していくわけだが、今回は、「そもそも、アプリケーションを動作させる母体であるWindows OSは、どのようにして動いているのか?」ということについて、次のページで解説しよう。


パソコンの歴史と動作原理

 Windows OSは、基本的に、「パソコン」と呼ばれる家電品の上で動作する。パソコンは、いまでは多くの電器店に陳列されて販売されているので、誰でもいつでも手軽に入手して、すぐに使うことができる。しかしパソコンが、現在のように一般消費者の手元に届くようになるまでには、コンピュータの長い進化の歴史がある。コンピュータがパソコンへと発展していった歴史を、ここで簡単に振り返ってみることにしよう。

●コンピュータの発展とパソコンの登場

 1950~1960年ごろの初期のコンピュータは非常に巨大で、それだけでコンピュータ室全体を占拠しまうほどのサイズだった(筆者は実際に見たことはないが、読んだところ によると、最も大きいものでは高さが3メートル、幅が15メートルもあったというのだから、その幅を縦向きにするとビル5~6階建てくらいほどはあるだろうか……)。それほどまでに大きかったため、当然ながら、個人で所有することなどはとても考えられなかった。またその値段も個人ではとても買える水準ではなかった。

 しかし1970年代に入ると「マイクロ・コンピュータ」(略して、マイコン)と呼ばれる「小型コンピュータ」が作られるようになり、1977年4 月にはアップル社によって開発された「アップルII」という「個人向けコンピュータ」が市場に登場した(Apple社は、現在はMac OS を提供している)。このとき、アップル社を率いるスティーブ・ジョブズによって初めて使われた名称が「パーソナル・コンピュータ 」(前出のように「パソコン」とも略される。以降、「PC」と表記)といわれている。その後、IBMが開発したPC(IBM PC )が登場し、これと互換性のある「PC/AT 互換機 」がさまざまなメーカーから発売された。現在“PC”といえば、多くの場合このIBM PCの流れをくむものを指す。

 PCの性能は時代とともに飛躍的に向上していき、さらには複数のPCをネットワークでつなげて利用することが一般化していった。そして現在の企業システムでは、Windows登場以前では主流だった大型コンピュータ(=メインフレーム )や、メーカー固有の仕様によるコンピュータ(=オフコン )に取って代わり、小型で汎用的な仕様であるPCを使うことが一般的になっている。

巨大なメインフレームから個人で所有できるPCへ

●PCの基本的な構成内容

 それではPCは、一体どのようにして動作しているのだろうか。それを知るためには、PCの基本的な構成要素を知っておく必要がある。これについて、次のイラストと表にまとめた。一通りざっと確認してほしい。

ハードウェア名 機能名 説明
CPU (中央演算処理装置:Central Processing Unit) 演算・制御装置 PC上で演算処理を行ったり、
ほかのすべての装置を
制御したりするための装置。
この装置こそがPCのコアと
なる頭脳部分である
メイン・メモリ 主記憶装置 PC上で何らかの処理作業
を行う場合に、一時的な
データを記憶するために
使用される装置。ここに
置かれたデータは電源が
切れると消失する。
なお、単に「メモリ」と呼ぶ
場合、通常はこの
「メイン・メモリ」を指す
ハード・ディスク 、CD-ROM /DVD-ROM など 補助記憶装置 PC上で動作する
プログラムが使用する
記録用データを保存したり、
取得したり
(=読み出したり)するため
の装置。またWindows OS
やプログラム自体の
プログラム・データ
(=ソフトウェア)もここに
格納される。ここに置かれた
データは電源が切れても
なくならない
キーボード、マウスなど 入力装置 コンピュータへの入力を
行うための装置。これにより、
ユーザーがコンピュータの
処理・動作を自由に操作できる
ディスプレイ(画面出力)、プリンタ(印刷)など 出力装置 コンピュータからの出力を
行うための装置。これにより、
ユーザーはコンピュータの
処理結果を参照したり、
活用したりできるようになる
PCの基本的な構成内容
本表で示しているのは、PCを動作させるために、最低限必要なPCの
構成要素である。なお、これらの装置のように物理的で有形なものは、
「ハードウェア 」と呼ばれる。一方、アプリケーション(プログラム)や
データなど目に見えないものは、「ソフトウェア 」と呼ばれる。

 以上のPCの構成内容・構造を頭の中に入れたうえで、それをイメージしながら以下の解説を読んでほしい。

●Windows OSが起動するための前提条件

 最初に意識しなければならないのは、次の事実・前提条件である。


  • PCを動かすためのソフトウェア「Windows OS」そのものは、補助記憶装置である「ハード・ディスク」内に格納(=インストール)されている

 この前提条件からスタートして、OSが起動するまでの流れを考えてみよう(なお以下の説明は、厳密な動作内容を表すものではなく、あくまで処理の流れを簡略化して表現したものなので、実際の処理の流れとは完全に一致しない。注意すること)。

●Windows OSが立ち上がるまでの処理の流れ

 PCの電源を入れると、次の流れで処理が進行する。


演算・制御装置である「CPU」が、Windows OSのプログラム・データ(=ソフトウェア)を、「ハード・ディスク」から取得して、作業用の「メイン・メモリ」の上にロード(=展開)する

CPUが「メイン・メモリ」からWindowsのプログラム・データの処理命令を<フェッチ>して(=取り出して)、その命令内容を<デコード>(=解釈)する*6

CPUがデコードした命令内容を<実行>していくことで、プログラム(=Windows OS)が開始される


PCの電源を入れた後のWindowsが立ち上がるまでの処理の流れ

 この作業が完了すれば、Windows OSが立ち上がることになる。

*6 Windowsのプログラム・データの処理命令はCPUが解釈できる「マシン語(機械語) 」で記述されている。このマシン語の命令セット(=処理命令の言語体系)は、CPUのアーキテクチャによって異なる。なお、Windowsで使われている CPUのアーキテクチャは「x86 」と呼ばれる。

 この処理の流れは、OSの起動時だけでなく、次に記すようにアプリケーションを実行する場合でも同様だ。

●Windows上でプログラムが実行されるまでの処理の流れ

 実際にWindows上でプログラムを実行すると、次のような流れになる。


プログラムの実行に必要なプログラム・データを「メイン・メモリ」の上にロードする

CPUが「メイン・メモリ」からプログラム・データの処理命令を<フェッチ>して、その命令内容を<デコード>する

CPUがデコードされた命令内容を<実行>していくことで、プログラムが開始される


プログラムが立ち上がるまでの処理の流れ

 次回では、このプログラムの実行処理の流れを、Windows OSから実行されるという視点で、より詳しく解説していくつもりだ。

■

 以上で今回は終了である。次回は「Windows上でアプリケーションが実行される仕組み」を解説する。これは.NETアプリケーションが.NETで動作し始める前段階の処理過程の説明である。次回は若干難しい話になってくるのである程度覚悟しておいてほしい。End of 
Article