AnyCPUのモジュールからx86とx64のモジュールを呼び分ける

提供:MochiuWiki : SUSE, EC, PCB
2021年11月24日 (水) 18:08時点におけるWiki (トーク | 投稿記録)による版 (文字列「</source>」を「</syntaxhighlight>」に置換)
ナビゲーションに移動 検索に移動

概要

C#には、実行環境が32bitや64bitを問わず、適切なモードで実行できるAnyCPUという仕組みが存在する。
しかし、この機能は実行時まで32bitまたは64bitのどちらで動作するかは不明なので、
AnyCPUをサポートしていない言語、例えばC/C++言語やC++CLI言語で作成したDLLを使用する場合、DllImportとの相性が悪い。
なぜなら、DllImportはコンパイル時にDLLの読み込みパスを指定するため、
実行時に”32bitだったらこのDLL、64bitだったらこのDLL”ということが出来ない。

このような場合、どのような解決策を使うにせよ、何らかの制約を強いられることになる。 いくつかある解決策の長所および短所を以下に纏めた。

すべてのモジュールを32bitで固定する方法

C#側を32bitに固定することで、常に32bitのDLLを使用する方法。

  • 長所:一番単純で、最低限の変更で済む。
  • 短所:64bitOSの長所(大量のメモリが使用できて一部の処理が高速化する)を活かせない。



C#側のモジュールも32bitと64bitを別々に用意する方法

すべてのモジュールに対して32bitと64bitを別々に作る方法。
最初に起動するexeファイルが32bitまたは64bitでのビルドになっていれば、それ以降に読み込まれるAnyCPUのDLLもそれに応じて動作するため、
AnyCPUなDLLが大量にある場合もそれらはすべてコピーで問題ない。

  • 長所:ユーザに選択を任せることで、プログラム側で判断をする必要から解放される。
  • 短所:Webサイトに公開する場合などに、ユーザは自分の環境に合わせたバージョンをDLして貰わねばならず面倒をかける。



DLLをSystemディレクトリに配置する方法

64bit版WindowsにはWOW64があり、通常の64bitのSystem32ディレクトリとは別に、
32bitのプログラムを動かすためにSysWOW64ディレクトリが存在するので、そのディレクトリに別々にDLLを配置する方法。

  • 長所:ソースコードの修正が不要である。
  • 短所:Systemディレクトリが汚れる。また、セキュリティ関連の警告が表示され、アンインストールも大変になる。



インストーラを使用する方法

インストーラを使えば、インストーラが実行時に実行環境を判別して、適切なDLLを配置してくれるように設定可能で、不要なDLLは残らない。

  • 長所 : 不要なDLLが残らない。
  • 短所 : インストーラを作る必要がある。レジストリにインストール情報が残る。

     32bit用DLLと64bit用DLLの両方を作成しなければいけない。

SetDllDirectory関数を使用する方法

32bitのDLLと64bitのDLLを別のディレクトリに配置して呼び分ける方法。
DLLの配置場所を変更するAPIである”SetDllDirectory”関数を用いて、実行時にそれぞれの置き場を指定する。
以下にソースコードを記載する。

 // <summary>
 // DllImport用に、x86用のDLLのあるディレクトリとx64用のDLLのあるディレクトリを設定するためのクラスです。
 // </summary>
 public static class NativeDllDir
 {
    // <summary>
    // DllImport用に、x86用のDLLのあるディレクトリとx64用のDLLのあるディレクトリを設定します。
    // </summary>
    // <param name="x86DllDir">x86用のDLLを配置したディレクトリを指定します。 
    // 指定しなければカレントディレクトリとなります。
    // </param>
    // <param name="x64DllDir">x64用のDLLを配置したディレクトリを指定します。
    //			  指定しなければカレントディレクトリとなります。
    // </param>
    // <returns>設定に成功したらtrue。</returns>
    // <exception cref="PlatformNotSupportedException">x86でもx64でもない場合の例外です。</exception>
    public static bool Set( string x86DllDir = null, string x64DllDir = null )
    {
       // 既に設定されているものをリセット
       SetDllDirectory( null );
 
       if ( IntPtr.Size == 8 )
       { // 64bit
          return SetDllDirectory( string.IsNullOrEmpty( x64DllDir ) ? "." : x64DllDir );
       }
       else if ( IntPtr.Size == 4 )
       { // 32bit
          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
 {
    // <summary>
    // アプリケーションのメイン エントリ ポイントです。
    // </summary>
    [STAThread]
    static void Main( string[] args )
    {
       // exeファイルが存在するディレクトリパス
       string MyPath = System.IO.Path.GetDirectoryName(
                       System.Reflection.Assembly.GetEntryAssembly().Location );
       NativeDllDir.Set( MyPath + @"\x86", MyPath + @"\x64" );
       // TODO: この後でDllImportを定義しているクラスを使用
    }
 }
  • 長所 : SetDllDirectory関数の呼び出しコードを追加すればよいだけなので、実装が比較的容易である。
  • 短所 : Windows XP SP1以降でのみ動作する。それ以前ではSetDllDirectory関数は使用できない。

     32bit用DLLと64bit用DLLの両方を作成しなければいけない。

32bit用DLLと64bit用DLLの両方をDllImportする方法

ソースコードは上記より短いが中途半端で使用し難い。

<syntaxhighlight lang="cpp">
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;
      }
   }
}
  • 長所 : Windows XP SP1より以前でも使用できる。
  • 短所 : 32bit用DLLと64bit用DLLの両方を作成しなければいけない。また、呼び出す関数をすべて記載しなければいけない。