この記事では.NETにおける64ビットプロセスと32ビットプロセスについて説明をおこなう。
1プロセスにおける32ビットと64ビットの混在
ネィティブアプリケーションの場合、プラットフォームの異なるExeとDllは共存できません。
・x64のExeと x64のDLL ⇒動作する
・x86のExeと x86のDLL ⇒動作する
・x64のExeと x86のDLL ⇒動作しない
・x86のExeと x64のDLL ⇒動作しない
ネイティブアプリケーションはビルド時に、どちらにするか指定してビルドする必要があります。
.NET の場合、ビルド時にプラットフォームの"x86","x64"以外に"Any CPU"が選択できます。
Any CPUを選択した場合次のような挙動になります。
・Exeの場合、OSが32ビットの場合、32ビットのプロセスとして動作します。
・Exeの場合、OSが64ビットの場合、64ビットのプロセスとして動作します。
・DLLの場合、呼び出したExeが32ビットで動作している場合、32ビットで動作します。
・DLLの場合、呼び出したExeが64ビットで動作している場合、64ビットで動作します。
Any CPUでExeを作成した場合、64bitOSで32bitプロセスを動作させることは出来ません。
この問題を解決するために、.NET4.5以降では「32ビットを優先にする」というオプションを指定できます。
これはAny CPUではありますが、32bitで動作が可能な場合は32bitで動作します。
64bitOSでAnyCPUでコンパイルした32bitプロセスをビルドなしに動かす方法
CorFlags を用いてExeを修正することが可能です。
このコマンドはVisualStudio2008の場合は下記にあります。
C:\Program Files (x86)\Microsoft SDKs\Windows\v8.0A\bin\NETFX 4.0 Tools\CorFlags.exe
VisualStdioをインストール時のプログラムファイルに登録されるされるコマンドプロンプトではパスが通っています。
使用例:
CorFlags /32BIT+ TestR.exe
VS2012では /32BITREQ+ フラグになります。
もしDLLを含む場合、そのDLLがx64でビルドされているようなら一緒に変更しないといけません。
AnyCPUでビルドされているDLLは、ExEのプラットフォームに合わせて動作するので不要です。
もし、強い名称で署名をされている場合は、/Force オプションを付与する必要があります。
CorFlags /32BIT+ /Force RDotNet.dll
プラットフォームの異なるプロセスの相互運用
1つのプロセスで32bitと64bitは混在して動作しないことは説明しました。
しかし、64ビット版のWindowsでは64bitプロセスと32ビットプロセス間のプロシージャコールはサポートされています。
つまり64ビットのアウトプロセスのCOMサーバーは32ビットのクライアントと通信でき、逆に32ビットのアウトプロセスのCOMサーバーは64ビットのクライアントと通信できます
.NETでCOMサーバーを作成するのは下記を参考にしてください。
How to develop an out-of-process COM component by using Visual C++, Visual C#, or Visual Basic .NET
http://support.microsoft.com/kb/977996
実際にCOMサーバーを作成するには、上記のページからCSExeCOMServerをダウンロードします。
プロジェクトのプロパティーを以下のいづれかに修正してください。
32ビットのアウトプロセスを作成する場合
「ビルド」タブのプラットフォームターゲット
x86
「ビルドイベント」タブの「ビルド後に実行するコマンドライン」
C:\Windows\Microsoft.NET\Framework\v2.0.50727\regasm.exe /tlb "$(TargetPath)"
64ビットのアウトプロセスを作成する場合
「ビルド」タブのプラットフォームターゲット
x64
「ビルドイベント」タブの「ビルド後に実行するコマンドライン」
C:\Windows\Microsoft.NET\Framework64\v2.0.50727\regasm.exe /tlb "$(TargetPath)"
実際にビルドしてCOMを登録すると、32ビットのCOMサーバー、または64ビットのCOMサーバーどちらであっても、32ビット、64ビットの両方のクライアントから使用できることがわかります。
詳しい詳細は、ダウンロードしたReadme.txtを参考にしてください。実際の作り方が記述してあります。
なお、アウトプロセスのCOMサーバーなので以下のようにスタティックな変数を使用すれば、異なるプラットフォームのプロセス間でのデータの共有が可能です。
public class CSSimpleObject : ReferenceCountedObject, ICSSimpleObject
{
private static int _staticVal = 0;
private static Object thisLock = new Object();
public int StaticValue
{
get
{
return _staticVal;
}
set
{
lock(thisLock)
{
_staticVal = value;
}
}
}
WindowsではこのようにアウトプロセスのCOMサーバーを利用することにより、異なるプラットフォームであっても、労力をかけずに連携できると思います。
アウトプロセスのCOMではプラットフォームターゲットでAnyCPUを使用してはいけない。
COMオブジェクト作成中にアプリケーションがハングします。
PEヘッダ中のIMAGE_NT_HEADERS.FileHeader.Machineに IMAGE_FILE_MACHINE_I386がセットされているためです。
64ビットOSの場合、COMとしては32ビットとして動作することを期待していますが、実際64bitとして動作してしまうのです。
この値はVisualStdioに付属しているbumpbinを用いて確認できます。以下のようなコマンドを実行してください。
dumpbin CSExeCOMServer.exe /headers
この結果を見てみましょう
x86を明示した場合:
File Type: EXECUTABLE IMAGE
FILE HEADER VALUES
14C machine (x86)
3 number of sections
x64を明示した場合
File Type: EXECUTABLE IMAGE
FILE HEADER VALUES
8664 machine (x64)
2 number of sections
Any Cpu
File Type: EXECUTABLE IMAGE
FILE HEADER VALUES
14C machine (x86)
3 number of sections
Any Cpuのヘッダにはx86と記述されていますが、実際、.NETとしては64ビットで動作するために不整合が出てしまいます。
アウトプロセスのCOMを作成する場合は、プラットフォームは絶対に指定してビルドをしなければなりません。
応用例
32ビットプロセスと64ビットプロセスはCOMを用いれば連携して動作させることはレガシーなコードの1部をCOMとして切り出して、.NETで置き換えるという手法がとれます。
たとえばVisualBasic6.0のプロセスは64bitプロセスでは動作しません。しかし、大量にメモリを使用する機能を追加するにはどうするか考えましょう。
一つの回答として、メモリを大量に消費する箇所のみCOMサーバーとして切り出すという選択肢がとれます。