AnyCPUのモジュールからx86とx64のモジュールを呼び分ける
概要
C#では、実行環境のアーキテクチャがx86 / AMD64に関わらず、適切なモードで実行できるAnyCPUという仕組みが存在する。
しかし、AnuCPUは実行時までx86 / AMD64のどちらで動作するかは不明である。
そのため、AnyCPUをサポートしていない言語、例えば、C/C++言語やC++/CLI言語で開発されたライブラリを使用する場合、DllImport
キーワードとの相性が悪い。
なぜなら、DllImport
キーワードはコンパイル時にライブラリの読み込みパスを指定するため、実行時において、x86 / AMD64ごとにライブラリを切り替えることができない。
このような場合、どのような解決策を使うにせよ、何らかの制約を強いられることになる。
全てのモジュールをx86に固定する方法
C#側の実行ファイルをx86に指定することにより、常にx86のライブラリを使用する方法である。
- メリット
- 最も簡単であり、最低限の変更で済む。
- デメリット
- 64ビットOSのメリット (多くのメモリが使用でき、一部の処理が高速化する) を活かせない。
C#側のモジュールもx86 / AMD64を別々に用意する方法
全てのモジュールに対して、x86 / AMD64を別々に開発する方法である。
最初に起動する実行ファイルがx86またはAMD64でのビルドになっている場合、それ以降に読み込まれるAnyCPUのライブラリもそれに応じて動作する。
そのため、AnyCPUのライブラリが多く存在する場合もそれらは全てコピーで問題ない。
- メリット
- ユーザに選択を任せることにより、プログラム側で判断をする必要から解放される。
- デメリット
- Webサイトに公開する場合等、ユーザは自分の環境に合わせたバージョンをダウンロードする必要がある。
DLLをSystemディレクトリに配置する方法
AMD64向けWindowsにはWOW64機能があり、AMD64のSystem32ディレクトリとは別に、x86のプログラムを動作させるためにSysWOW64ディレクトリが存在する。
これを利用して、該当するディレクトリに対して、別々にライブラリを配置する方法である。
- メリット
- ソースコードの修正が不要である。
- デメリット
- Systemディレクトリが汚れる。
- セキュリティ関連の警告が表示される。
- また、アンインストールも煩雑になる。
インストーラを使用する方法
インストーラを使用する場合、インストーラが実行時に実行環境を判別して適切なライブラリを配置するように設定可能なため、不要なライブラリは残らない。
- メリット
- 不要なライブラリが残らない。
- デメリット
- インストーラを作成する必要がある。
- レジストリにインストール情報が残る。
- x86向けライブラリおよびAMD64bit向けライブラリの両方を開発しなければならない。
SetDllDirectory関数を使用する方法
x86 / AMD64のライブラリを別のディレクトリに配置して呼び分ける方法である。
ライブラリの配置場所を変更するAPIであるSetDllDirectory
関数を使用して、実行時にそれぞれのライブラリのパスを指定する。
// <summary>
// DllImport向けに、x86 / AMD64向けのライブラリを別々に設定するためのクラス
// </summary>
public static class NativeDllDir
{
// <summary>
// DllImport向けに、x86 / AMD64のライブラリのあるディレクトリを設定する
// </summary>
// <param name="x86DllDir">x86 DLLを配置したディレクトリを指定する
// 指定しなければカレントディレクトリとなる
// </param>
// <param name="x64DllDir">AMD64 DLLを配置したディレクトリを指定する
// 指定しなければカレントディレクトリとなる
// </param>
// <returns>設定に成功した場合は true</returns>
// <exception cref="PlatformNotSupportedException">x86でもAMD64でもない場合の例外</exception>
public static bool Set( string x86DllDir = null, string x64DllDir = null )
{
// 既に設定されているものをリセット
SetDllDirectory( null );
if ( IntPtr.Size == 8 )
{ // AMD64
return SetDllDirectory( string.IsNullOrEmpty( x64DllDir ) ? "." : x64DllDir );
}
else if ( IntPtr.Size == 4 )
{ // x86
return SetDllDirectory( string.IsNullOrEmpty( x86DllDir ) ? "." : x86DllDir );
}
else
{ // その他
throw new PlatformNotSupportedException();
}
}
[System.Runtime.InteropServices.DllImport( "kernel32", SetLastError = true )]
private static extern bool SetDllDirectory( string lpPathName );
}
static class Program
{
[STAThread]
static void Main( string[] args )
{
// exeファイルが存在するディレクトリパス
string MyPath = System.IO.Path.GetDirectoryName(
System.Reflection.Assembly.GetEntryAssembly().Location );
NativeDllDir.Set( MyPath + @"\x86", MyPath + @"\x64" );
// DllImportを定義しているクラスを使用
// ...略
}
}
- メリット
SetDllDirectory
関数の呼び出しコードを追加するだけのため、実装が比較的容易である。
- デメリット
- Windows XP SP1以降でのみ動作する。
- それ以前のOSの場合は、
SetDllDirectory
関数は使用できない。 - x86向け / AMD64向けのライブラリの両方を開発しなければならない。
x86向けDLLとx64向けDLLの両方をDllImportする方法
"SetDllDirectory関数を使用する方法"より簡単に記述可能であるが、中途半端で使用しにくい。
- 長所
- Windows XP SP1より以前でも使用できる。
- 短所
- x86向けDLLとx64向けライブラリの両方を開発する必要がある。
- また、呼び出す関数を全て記述する必要がある。
public class DLL
{
[DllImport(@"x86\hoge.dll", EntryPoint = "Hoge")]
private static extern IntPtr Hoge32();
[DllImport(@"x64\hoge.dll", EntryPoint = "Hoge")]
private static extern IntPtr Hoge64();
public delegate IntPtr HogeFunc();
public static readonly HogeFunc Hoge;
static Dll()
{
if ( IntPtr.Size == 8 )
{
Hoge = Hoge64;
}
else if ( IntPtr.Size == 4 )
{
Hoge = Hoge32;
}
}
}