Part 2. .NET Framework 2.0 アプリケーションの 64 ビット対応

では引き続き、Part 2. のエントリでは、.NET Framework 2.0 のアプリケーションがどのようにして 64 ビット(または 32 ビット)で動作するのか、についてみていくことにします。.NET Framework では、IL (中間言語)と呼ばれるものが利用されているため、x86, x64, IA64 すべてに対応する .exe ファイルや .dll ファイルを作成することができます。にもかかわらず、Visual Studio を開くと、

image

……と、こんなスイッチがあるわけなのですが、いったいこれは何なのか? これを理解するためには、.NET Framework のコンパイル動作を正しく把握する必要があります。これについて以下の手順で解説していくことにします。

  • IL コードとネイティブコード
  • コンソールアプリケーションにおけるコンパイルスイッチの意味
  • ライブラリアプリケーションにおけるコンパイルスイッチの意味 

[Step 7. IL コードとネイティブコード]

.NET Framework の開発言語である C# や VB で書いたコードは、コンパイルしてもすぐに x86 や x64 などの命令コード(ネイティブコード)には変換されません。

  • .NET のアプリケーションコードはまずコンパイル時に MSIL (Microsoft Intermediate Language)と呼ばれる中間言語コードに変換されます。
  • そして実行時に CLR ランタイムがこれをネイティブコード(x86 や x64 用の命令コード)に変換し、そして CPU 上で実行します。

image

このため、.NET で書かれた .exe ファイルや .dll ファイルは、x86 Windows OS 上で動作させれば 32 ビット動作し、x64 Windows OS 上で動作させれば 64 ビット動作します。つまり、ひとつのファイルで、32 / 64 ビット両方の動作をさせることができるようになっています

[Step 8. コンソールアプリケーションにおけるコンパイルスイッチの意味]

一般に、アプリケーションプログラムが MSIL のみで記述されている、つまり完全に .NET コードのみで記述されている場合には、上記のような動作は全く問題がありません。しかし、この .NET のアプリケーションが周辺のライブラリファイルなどを利用している場合には、問題が生じることがあります。

例えば、.NET のコンソールアプリケーションが Access .mdb ファイルにアクセスするために内部で Jet OLE DB プロバイダを使っている場合を考えてみましょう。実は、Jet OLE DB プロバイダは 64 ビット版ドライバが提供されていません。このため、.NET アプリケーションが 64 ビット動作してしまうと、ドライバが見つからずにエラーが発生する、という現象が起こります。

imageimage

となると、このようなコンソールアプリケーションは、x64 Windows OS 上であっても 64 ビット動作されては困る、ということになります。このような場合に利用するのが、最初に示したプラットフォームスイッチです。Visual Studio 2008 のツールバーには、下図に示すようなプラットフォームスイッチがあります。

image

ここから構成マネージャを開き、アクティブソリューションプラットフォームを追加していくと、CPU 名を追加していくことができます。

  • "Any CPU" とは、「実行時に、どのような CPU 向けのネイティブコードを作成してもよい」というスイッチです。これを指定しておくと、x86 版 Windows 上で実行したときは x86 コードに、x64 版 Windows 上で実行したときは x64 コードに変換されて実行されるようになります。
  • それ以外のスイッチは、「どんな環境だろうと、指定した CPU 向けのネイティブコードしか作成しちゃいけません」というスイッチです。例えば "x86" を指定しておくと、x64 版 Windows 上で実行した場合でも、強制的に x86 コードに変換されて(WOW 上で)実行されるようになります。

image

64 ビット版 Windows OS でも、32 ビット版の Jet OLE DB プロバイダであれば同梱されています。このため、先の Jet OLE DB プロバイダを使うアプリケーションの場合には、このスイッチとして "x86" を指定しておき、32 ビットコードに変換されるようにしておかないと、64 ビット OS 上で正しく動作させることができなくなります。

ここで留意していただきたいのは、このプラットフォームスイッチは**アセンブリファイルに出力される MSIL コードそのものを変えるものではない**、という点です。このプラットフォームスイッチで変更されるのは PE ヘッダ情報内のフラグである、という点に注意してください。

ですので、プラットフォームスイッチを変えなければならないのは、以下のようなケースに限られます。

  • 64 ビット版が存在しないライブラリを使っているアプリケーション
    典型的なものとしては、ODBC ドライブを使っているもの(ODBC は 32 ビット版ドライバしか存在しない)、Jet OLE DB や Excel OLE DB を使っているもの。
  • 32 ビット版しか存在しない COM を InProc で使っているアプリケーション
    例えば ADO などは 64 ビット版がちゃんと提供されていますが、サードパーティ製の COM は 32 ビット版しか提供されていないものの方が多いです。
  • unsafe ブロックを使っていたり、ポインタ操作(IntPtr)をしているアプリケーション
    32 ビット環境での動作しか考えられていないポインタ操作などが含まれている場合には、C# や VB のみで完遂しているアプリケーションであってもプラットフォームスイッチの指定が必要です。

x86 オプションをつけないと危険なものについては、Migrating 32-bit Managed Code to 64-bit などの資料に取りまとめられていますので、こちらについても参照してみるとよいでしょう。

ちなみにこのような理由から、32 ビットマシンや 32 ビット OS を使っている場合であっても、64 ビット C# アプリケーションのコンパイルができます。なぜなら、単にヘッダ情報を "x64" として MSIL コードを出力するだけだからです。(もちろん、出力された .exe ファイルは 32 ビット OS 上では動作しませんが。)

[Step 9. ライブラリアプリケーションにおけるコンパイルスイッチの意味]

上記の解説を踏まえれば、ライブラリアプリケーション(dll ファイル)におけるスイッチの意味についても明らかです。このスイッチは、要するにどのようなプロセスからこの dll ファイルを利用してよいのか、を規定するものになります。

例えば、下図のように Jet OLE DB プロバイダを利用するクラスライブラリ(dll ファイル)をコンパイルする場合には、"x86" オプションをつけておくべきです。なぜなら、64 ビットプロセスがこの dll ファイルを呼び出しても、正しく動作することができないからです。

image

当たり前のことですが、.dll ファイルは、プロセスからロードされて利用されるライブラリです。なので、クラスライブラリ側に、プロセスの動作モード(32 ビット/64 ビット)を切り替える権利や機能はありません。最初に .exe ファイルを起動した時点で、プロセスの動作モード(32 or 64 ビット)はすでに決定しており、そこから呼び出せるかどうかが .dll ファイルのスイッチにより決定されます。以下に、.exe ファイルと .dll ファイルのスイッチの組み合わせ例を示しますので、じっくり見てみてください。

image

[Step. 10] 動作モードやヘッダフラグの確認方法

なお、アプリケーションの内部から、現在自分が 64 ビット/32 ビットどちらで動作しているのかを知る一番簡単な方法は、IntPtr.Size プロパティをチェックするというものです。

  • IntPtr.Size = 8 の場合は 64 ビットモードでプロセスが動作している。
  • IntPtr.Size = 4 の場合は 32 ビットモードでプロセスが動作している。

この方法では、x64, IA-64 の区別はつきませんが、通常はこれで十分でしょう。(どうしてもそこまで切り分けたい場合には、GetSystemInfo() Win32 API を利用します。)

また、PE ヘッダの確認を行う場合には、Windows SDK に含まれる CorFlags.exe ツールを利用します。これを利用すると、Any CPU, x86, x64/Itanuim などのいずれのヘッダを持つのかを比較的簡単に見分けることができます。(この中の PE, 32BIT フラグに着目します。こちらの方法も x64, Itanium を見分けられないのがちょっと残念な感じですが;)

 C:\>corflags "C:\Documents and Settings\nakama\My Documents\Visual Studio 2005\P
 rojects\ConsoleApplication1\ClassLibrary1\bin\x86\Debug\ClassLibrary1.dll"
 Microsoft (R) .NET Framework CorFlags Conversion Tool.  Version  2.0.50727.42
 Copyright (c) Microsoft Corporation.  All rights reserved.
  
 Version   : v2.0.50727
 CLR Header: 2.5
 PE        : PE32
 CorFlags  : 3
 ILONLY    : 1
 32BIT     : 1
 Signed    : 0

image

[ここまでのまとめ]

では、今回のエントリのポイントの復習です。

  • .NET アプリケーションのアセンブリファイルは、実行時にネイティブコードに変換されます。このため、x64, x86 の両方に対応するアセンブリファイルを作成することができます。
  • 既定値である Any CPU 設定でコンパイルを行うと、実行時に x86, x64 のどちらのネイティブコードも生成してくれます。
  • C# や VB のコードのみで完結しているアプリケーションであれば 32, 64 ビットどちらでも動作できますが、Unmanaged ライブラリを呼び出している場合(Jet OLE DB など)には、x64, x86 の両方に対応できないアプリケーションとなる場合があります。
  • 特定プラットフォームでしか動作しないアプリケーションを作成する場合には、コンパイルプション(x86, x64 など)により、ヘッダ部に情報を付与しておく必要があります。
  • プロセスタイプは、プロセスの起動時に決定されます。このため、例えばコンソールアプリ(.exe)からライブラリ(.dll)を呼び出す場合、プロセスタイプはコンソールアプリ起動時に決定されます。

と、長々と書きましたが、要点をまとめるとたったひとつです。

「特定プラットフォームでしか動作しないモジュールを作る場合には、必ず x86, x64 などのオプションをつけてコンパイルすること」

この一点だけをちゃんと守ってもらえれば OK です。

……がしかし、IIS 上で動作する ASP.NET アプリケーションの場合はちょっと話が複雑になります。これについては次回のエントリにて^^。