「AnyCPUのモジュールからx86とx64のモジュールを呼び分ける」の版間の差分

📢 Webサイト閉鎖と移転のお知らせ
このWebサイトは2026年9月に閉鎖いたします。
新しい記事は移転先で追加しております。(旧サイトでは記事を追加しておりません)

文字列「</source>」を「</syntaxhighlight>」に置換
編集の要約なし
 
(同じ利用者による、間の2版が非表示)
1行目: 1行目:
== 概要 ==
== 概要 ==
C#には、実行環境が32bitや64bitを問わず、適切なモードで実行できるAnyCPUという仕組みが存在する。<br>
C#では、実行環境のアーキテクチャがx86 / AMD64に関わらず、適切なモードで実行できるAnyCPUという仕組みが存在する。<br>
しかし、この機能は実行時まで32bitまたは64bitのどちらで動作するかは不明なので、<br>
<br>
AnyCPUをサポートしていない言語、例えばC/C++言語やC++CLI言語で作成したDLLを使用する場合、DllImportとの相性が悪い。<br>
しかし、AnyCPUは実行時までx86 / AMD64のどちらで動作するかは不明である。<br>
なぜなら、DllImportはコンパイル時にDLLの読み込みパスを指定するため、<br>
これは、C/C++言語やC++/CLI言語で開発されたネイティブライブラリを使用する場合に問題となる。<br>
実行時に”32bitだったらこのDLL、64bitだったらこのDLL”ということが出来ない。<br><br>
ネイティブライブラリはコンパイル時に特定のアーキテクチャ (x86またはAMD64) 向けにビルドされるため、実行環境のアーキテクチャと一致しなければ動作しない。<br>
このような場合、どのような解決策を使うにせよ、何らかの制約を強いられることになる。
つまり、x86向けにビルドされたDLLは64ビットプロセスからは呼び出すことができず、逆にAMD64向けにビルドされたDLLは32ビットプロセスからは呼び出すことができない。<br>
いくつかある解決策の長所および短所を以下に纏めた。<br><br>
<br>
一方、C#の <code>DllImport</code> 属性は、コンパイル時にネイティブライブラリのパスと関数名を静的に指定する仕組みである。<br>
この指定は実行ファイルに組み込まれるため、実行時に動的に変更することができない。<br>
したがって、AnyCPUでビルドされたC#アプリケーションが実際に32ビットプロセスとして起動するか64ビットプロセスとして起動するかは実行時に決定されるにもかかわらず、<br>
<code>DllImport</code> で指定するDLLのパスはコンパイル時に固定されてしまうという矛盾が生じる。<br>
<br>
例えば、<code>[DllImport("MyNativeLib.dll")]</code> と記述した場合、この指定では単一のDLLパスしか指定できないため、<br>
実行時のアーキテクチャに応じて異なるDLL (例:x86版とAMD64版) を自動的に選択することができない。<br>
この制約により、AnyCPUとネイティブライブラリの組み合わせは相性が悪いと言える。<br>
<br>
このような場合、どのような解決策を使うにせよ、何らかの制約を強いられることになる。<br>
<br>
なお、.NET FrameworkにおけるAnyCPUには、<u>AnyCPU</u> と <u>AnyCPU 32bitPreferred</u> の2種類が存在する。<br>
<u>32bitPreferred</u> は64ビットOS上でも32ビットプロセスとして実行される設定であり、ARM環境等での互換性を考慮した設定である。<br>
<br><br>


== すべてのモジュールを32bitで固定する方法 ==
== 全てのモジュールをx86に固定する方法 ==
C#側を32bitに固定することで、常に32bitのDLLを使用する方法。<br><br>
C#側の実行ファイルをx86に指定することにより、常にx86のライブラリを使用する方法である。<br>
 
<br>
* '''長所:一番単純で、最低限の変更で済む。'''
* メリット
* '''短所:64bitOSの長所(大量のメモリが使用できて一部の処理が高速化する)を活かせない。'''
*: 最も簡単であり、最低限の変更で済む。
* デメリット
*: 64ビットOSのメリット (多くのメモリが使用でき、一部の処理が高速化する) を活かせない。
<br><br>
<br><br>


== C#側のモジュールも32bitと64bitを別々に用意する方法 ==
== C#側のモジュールもx86 / AMD64を別々に用意する方法 ==
すべてのモジュールに対して32bitと64bitを別々に作る方法。<br>
全てのモジュールに対して、x86 / AMD64を別々に開発する方法である。<br>
最初に起動するexeファイルが32bitまたは64bitでのビルドになっていれば、それ以降に読み込まれるAnyCPUのDLLもそれに応じて動作するため、<br>
<br>
AnyCPUなDLLが大量にある場合もそれらはすべてコピーで問題ない。<br><br>
最初に起動する実行ファイルがx86またはAMD64でのビルドになっている場合、それ以降に読み込まれるAnyCPUのライブラリもそれに応じて動作する。<br>
 
そのため、AnyCPUのライブラリが多く存在する場合もそれらは全てコピーで問題ない。<br>
* '''長所:ユーザに選択を任せることで、プログラム側で判断をする必要から解放される。'''
<br>
* '''短所:Webサイトに公開する場合などに、ユーザは自分の環境に合わせたバージョンをDLして貰わねばならず面倒をかける。'''
* メリット
*: ユーザに選択を任せることにより、プログラム側で判断をする必要から解放される。
* デメリット
*: Webサイトに公開する場合等、ユーザは自分の環境に合わせたバージョンをダウンロードする必要がある。
<br><br>
<br><br>


== DLLをSystemディレクトリに配置する方法 ==
== DLLをSystemディレクトリに配置する方法 ==
64bit版WindowsにはWOW64があり、通常の64bitのSystem32ディレクトリとは別に、<br>
AMD64向けWindowsにはWOW64機能があり、AMD64のSystem32ディレクトリとは別に、x86のプログラムを動作させるためにSysWOW64ディレクトリが存在する。<br>
32bitのプログラムを動かすためにSysWOW64ディレクトリが存在するので、そのディレクトリに別々にDLLを配置する方法。<br><br>
これを利用して、該当するディレクトリに対して、別々にライブラリを配置する方法である。<br>
 
<br>
* '''長所:ソースコードの修正が不要である。'''
* メリット
* '''短所:Systemディレクトリが汚れる。また、セキュリティ関連の警告が表示され、アンインストールも大変になる。'''
*: ソースコードの修正が不要である。
* デメリット
*: Systemディレクトリが汚れる。
*: セキュリティ関連の警告が表示される。
*: また、アンインストールも煩雑になる。
<br><br>
<br><br>


== インストーラを使用する方法 ==
== インストーラを使用する方法 ==
インストーラを使えば、インストーラが実行時に実行環境を判別して、適切なDLLを配置してくれるように設定可能で、不要なDLLは残らない。<br><br>
インストーラを使用する場合、インストーラが実行時に実行環境を判別して適切なライブラリを配置するように設定可能なため、不要なライブラリは残らない。<br>
 
<br>
* '''長所 : 不要なDLLが残らない。'''
* メリット
* '''短所 : インストーラを作る必要がある。レジストリにインストール情報が残る。'''
*: 不要なライブラリが残らない。
     '''32bit用DLLと64bit用DLLの両方を作成しなければいけない。'''
* デメリット
*: インストーラを作成する必要がある。
*: レジストリにインストール情報が残る。
*: x86向けライブラリおよびAMD64向けライブラリの両方を開発しなければならない。
<br><br>
<br><br>


== SetDllDirectory関数を使用する方法 ==
== SetDllDirectory関数を使用する方法 ==
32bitのDLLと64bitのDLLを別のディレクトリに配置して呼び分ける方法。<br>
x86 / AMD64のライブラリを別のディレクトリに配置して呼び分ける方法である。<br>
DLLの配置場所を変更するAPIである”SetDllDirectory”関数を用いて、実行時にそれぞれの置き場を指定する。<br>
<br>
以下にソースコードを記載する。<br>
ライブラリの配置場所を変更するAPIである<code>SetDllDirectory</code>関数を使用して、実行時にそれぞれのライブラリのパスを指定する。<br>
 
<br>
  <syntaxhighlight lang="cpp">
  <syntaxhighlight lang="c#">
  // <summary>
  /// <summary>
  // DllImport用に、x86用のDLLのあるディレクトリとx64用のDLLのあるディレクトリを設定するためのクラスです。
  /// DllImport向けに、x86 / AMD64向けのライブラリを別々に設定するためのクラス
  // </summary>
  /// </summary>
  public static class NativeDllDir
  public static class NativeDllDir
  {
  {
     // <summary>
     /// <summary>
     // DllImport用に、x86用のDLLのあるディレクトリとx64用のDLLのあるディレクトリを設定します。
     /// DllImport向けに、x86 / AMD64のライブラリのあるディレクトリを設定する
     // </summary>
     /// </summary>
     // <param name="x86DllDir">x86用のDLLを配置したディレクトリを指定します。
     /// <param name="x86DllDir">x86 DLLを配置したディレクトリを指定する
     // 指定しなければカレントディレクトリとなります。
     ///            指定しなければカレントディレクトリとなる
     // </param>
     /// </param>
     // <param name="x64DllDir">x64用のDLLを配置したディレクトリを指定します。
     /// <param name="x64DllDir">AMD64 DLLを配置したディレクトリを指定する
     //   指定しなければカレントディレクトリとなります。
     ///          指定しなければカレントディレクトリとなる
     // </param>
     /// </param>
     // <returns>設定に成功したらtrue。</returns>
     /// <returns>設定に成功した場合は true</returns>
     // <exception cref="PlatformNotSupportedException">x86でもx64でもない場合の例外です。</exception>
     /// <exception cref="PlatformNotSupportedException">x86でもAMD64でもない場合の例外</exception>
     public static bool Set( string x86DllDir = null, string x64DllDir = null )
     public static bool Set( string x86DllDir = null, string x64DllDir = null )
     {
     {
68行目: 94行目:
   
   
       if ( IntPtr.Size == 8 )
       if ( IntPtr.Size == 8 )
       { // 64bit
       { // AMD64
           return SetDllDirectory( string.IsNullOrEmpty( x64DllDir ) ? "." : x64DllDir );
           return SetDllDirectory( string.IsNullOrEmpty( x64DllDir ) ? "." : x64DllDir );
       }
       }
       else if ( IntPtr.Size == 4 )
       else if ( IntPtr.Size == 4 )
       { // 32bit
       { // x86
           return SetDllDirectory( string.IsNullOrEmpty( x86DllDir ) ? "." : x86DllDir );
           return SetDllDirectory( string.IsNullOrEmpty( x86DllDir ) ? "." : x86DllDir );
       }
       }
       else
       else
       { // その他
       { // その他
           throw new PlatformNotSupportedException();
           throw new PlatformNotSupportedException();
       }
       }
87行目: 113行目:
  static class Program
  static class Program
  {
  {
    // <summary>
    // アプリケーションのメイン エントリ ポイントです。
    // </summary>
     [STAThread]
     [STAThread]
     static void Main( string[] args )
     static void Main( string[] args )
97行目: 120行目:
                       System.Reflection.Assembly.GetEntryAssembly().Location );
                       System.Reflection.Assembly.GetEntryAssembly().Location );
       NativeDllDir.Set( MyPath + @"\x86", MyPath + @"\x64" );
       NativeDllDir.Set( MyPath + @"\x86", MyPath + @"\x64" );
       // TODO: この後でDllImportを定義しているクラスを使用
       // DllImportを定義しているクラスを使用
      // ...略
     }
     }
  }
  }
  </syntaxhighlight>
  </syntaxhighlight>
 
<br>
* '''長所 : SetDllDirectory関数の呼び出しコードを追加すればよいだけなので、実装が比較的容易である。'''
* メリット
* '''短所 : Windows XP SP1以降でのみ動作する。それ以前ではSetDllDirectory関数は使用できない。'''
*: <code>SetDllDirectory</code> 関数の呼び出しコードを追加するだけのため、実装が比較的容易である。
     '''32bit用DLLと64bit用DLLの両方を作成しなければいけない。'''
* デメリット
*: Windows XP with Service Pack 1 (SP1) 以降、または、Windows Server 2003以降でのみ動作する。
*: それ以前のOSの場合は、<code>SetDllDirectory</code> メソッドは使用できない。
*: x86向け および AMD64向けのライブラリの両方を開発しなければならない。
*: プロセス全体のDLL検索パスに影響を与えるため、他のライブラリの読み込みにも影響する可能性がある。
<br><br>
<br><br>


== 32bit用DLLと64bit用DLLの両方をDllImportする方法 ==
== x86向けDLLとAMD64向けDLLの両方をDllImportする方法 ==
ソースコードは上記より短いが中途半端で使用し難い。<br>
<code>SetDllDirectory</code> メソッドを使用する方法より簡単に記述可能であるが、中途半端で使用しにくい。<br>
 
<br>
  <syntaxhighlight lang="cpp">
* メリット
*: Windows XP SP1より以前でも使用できる。
* デメリット
*: x86向けDLLとAMD64向けライブラリの両方を開発する必要がある。
*: また、呼び出す関数を全て記述する必要がある。
<br>
  <syntaxhighlight lang="c#">
  public class DLL
  public class DLL
  {
  {
121行目: 156行目:
     public delegate IntPtr HogeFunc();
     public delegate IntPtr HogeFunc();
     public static readonly HogeFunc Hoge;
     public static readonly HogeFunc Hoge;
 
     static Dll()
     static Dll()
     {
     {
134行目: 169行目:
     }
     }
  }
  }
</syntaxhighlight>
<br><br>
== LoadLibrary / GetProcAddress 関数を使用した動的ロード方法 ==
<code>LoadLibrary</code> 関数 と <code>GetProcAddress</code> 関数 を使用して、実行時にライブラリと関数を動的にロードする方法である。<br>
<br>
<syntaxhighlight lang="c#">
public static class NativeLibraryLoader
{
    [DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode)]
    private static extern IntPtr LoadLibrary(string lpFileName);
    [DllImport("kernel32", SetLastError = true)]
    private static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName);
    [DllImport("kernel32", SetLastError = true)]
    private static extern bool FreeLibrary(IntPtr hModule);
    public static IntPtr LoadNativeLibrary(string basePath, string libraryName)
    {
      string path;
      if (IntPtr.Size == 8)
      {
          path = Path.Combine(basePath, "x64", libraryName);
      }
      else
      {
          path = Path.Combine(basePath, "x86", libraryName);
      }
      return LoadLibrary(path);
    }
}
</syntaxhighlight>
<br>
* メリット
*: 細かい制御が可能である。
*: 必要なタイミングでライブラリをロード / アンロードできる。
* デメリット
*: 実装が複雑になる。
*: デリゲートを使用して関数ポインタを取得する必要がある。
*: エラーハンドリングを適切に実装する必要がある。
<br><br>


* '''長所 : Windows XP SP1より以前でも使用できる。'''
== .NET 5以降での改善 ==
* '''短所 : 32bit用DLLと64bit用DLLの両方を作成しなければいけない。また、呼び出す関数をすべて記載しなければいけない。'''
.NET 5以降 (.NET Core 3.0以降) では、<code>NativeLibrary</code> クラスが導入され、ネイティブライブラリの読み込みがより容易になった。<br>
<br>
* メリット
*: クロスプラットフォーム対応である。
*: <code>RuntimeInformation</code> により、明示的なアーキテクチャ判定が可能である。
*: <code>NativeLibrary.TryLoad</code> メソッド や <code>NativeLibrary.Free</code> メソッド等の便利なメソッドが提供されている。
* デメリット
*: .NET Frameworkでは使用できない。
<br>
<syntaxhighlight lang="c#">
using System.Runtime.InteropServices;
public static class NativeLibraryHelper
{
    public static IntPtr LoadNativeLibrary(string basePath, string libraryName)
    {
      string path;
      if (RuntimeInformation.ProcessArchitecture == Architecture.X64)
      {
          path = Path.Combine(basePath, "x64", libraryName);
      }
      else if (RuntimeInformation.ProcessArchitecture == Architecture.X86)
      {
          path = Path.Combine(basePath, "x86", libraryName);
      }
      else
      {
          throw new PlatformNotSupportedException();
      }
     
      return NativeLibrary.Load(path);
    }
}
</syntaxhighlight>
<br><br>
<br><br>
{{#seo:
|title={{PAGENAME}} : Exploring Electronics and SUSE Linux | MochiuWiki
|keywords=MochiuWiki,Mochiu,Wiki,Mochiu Wiki,Electric Circuit,Electric,pcb,Mathematics,AVR,TI,STMicro,AVR,ATmega,MSP430,STM,Arduino,Xilinx,FPGA,Verilog,HDL,PinePhone,Pine Phone,Raspberry,Raspberry Pi,C,C++,C#,Qt,Qml,MFC,Shell,Bash,Zsh,Fish,SUSE,SLE,Suse Enterprise,Suse Linux,openSUSE,open SUSE,Leap,Linux,uCLnux,電気回路,電子回路,基板,プリント基板
|description={{PAGENAME}} - 電子回路とSUSE Linuxに関する情報 | This page is {{PAGENAME}} in our wiki about electronic circuits and SUSE Linux
|image=/resources/assets/MochiuLogo_Single_Blue.png
}}


__FORCETOC__
__FORCETOC__
[[カテゴリ:C_Sharp]]
[[カテゴリ:C_Sharp]]