AnyCPU DLL と P/Invoke の関係 ― なぜネイティブ DLL は両方必要なのか?

.NET の DLL は AnyCPU でビルドすることで、32bit / 64bit どちらの環境でも同じファイルを使い回せます。
しかし、P/Invoke(Platform Invocation Services) を使ってネイティブ DLL を呼び出す場合は、そう簡単にはいきません。

今回は「なぜネイティブ DLL を 32bit / 64bit 両方用意しなければならないのか?」を詳しく解説します。


1. AnyCPU の仕組みをおさらい

  • .NET の DLL(マネージド DLL)は、中間言語(IL)で保存されます。

  • 実行時に JIT コンパイラが、実行環境に合わせて 32bit ネイティブコード または 64bit ネイティブコード に変換します。

つまり AnyCPU DLL は自動的に「実行中のプロセスのビット数」に追従して動作するのです。


2. P/Invoke で起こる問題

P/Invoke を使うと、C#VB.NET のマネージドコードから C/C++ で書かれた ネイティブ DLL を呼び出せます。

例:

 
[DllImport("NativeLib.dll")]
public static extern int Add(int a, int b);

このとき、.NET ランタイムは 現在のプロセスのビット数と同じアーキテクチャの DLL をロードしようとします。

  • プロセスが 32bit → NativeLib.dll は 32bit DLL でなければならない

  • プロセスが 64bit → NativeLib.dll は 64bit DLL でなければならない

もし 32bit プロセスから 64bit DLL を読み込もうとすると、「不正なフォーマットです(BadImageFormatException)」 というエラーが発生します。


3. 具体例で考える

例えば、あなたが「数学演算ライブラリ」を作り、C# から C++ の関数を呼び出したいとします。

ネイティブ DLL 側(C++

 
// NativeLib.cpp
extern "C" __declspec(dllexport)
int Add(int a, int b)
{
  return a + b;
}

これを 32bit 用(x8664bit 用(x64) にそれぞれビルドします。

  • NativeLib32.dll

  • NativeLib64.dll


マネージド DLL 側(C#、AnyCPU)

 
public class MathWrapper
{
  [DllImport("NativeLib.dll", EntryPoint="Add")]
  public static extern int Add(int a, int b);
}

この DLL を AnyCPU でビルドすると:

  • 32bit アプリから呼ばれると、.NET ランタイムは「32bit DLL を探す」

  • 64bit アプリから呼ばれると、.NET ランタイムは「64bit DLL を探す」

つまり ネイティブ DLL を両方用意しなければならないということです。


4. 実運用での対応方法

方法1:DLL を分けて配置する

  • x86\NativeLib.dll

  • x64\NativeLib.dll

アプリの起動時に環境を判定し、DllImport のパスを切り替える。


方法2:同じ名前でシステムフォルダに配置する

Windows では、32bit と 64bit の DLL をそれぞれ専用のフォルダに置く仕組みがあります。

  • C:\Windows\System32 → 64bit DLL

  • C:\Windows\SysWOW64 → 32bit DLL

同じ名前の DLL を置いても、プロセスのビット数に応じて自動で切り替わります。


方法3:ランタイムで動的にロードする

LoadLibraryNativeLibrary.Load を使い、実行時に 32bit/64bit を判定して適切な DLL を明示的にロードする。


まとめ

  • AnyCPU DLL は「マネージドコード」部分だけなら両対応可能

  • しかし P/Invoke で呼ぶ「ネイティブ DLL」はプロセスのビット数に合わせたものが必要

  • つまり、実際の運用では ネイティブ DLL を 32bit / 64bit 両方用意して、状況に応じてロードさせる工夫が必要