◀ 4.

本番環境のdll内で参照しているdllファイルの置き場所

▶
この記事の要点
  • DLL が参照している別の DLL は、基本的にアプリの実行ファイル(.exe)と同じフォルダに置けば見つかる
  • 「参照元の DLL と同じフォルダ」ではなく「exe のフォルダ」が基準になる点が最大の落とし穴
  • ネイティブ DLL は exe のフォルダ → System32 → Windows フォルダ → カレント → PATH の順に探される
  • .NET のアセンブリは exe のフォルダ(アプリケーションベース)を探す。サブフォルダに置くなら app.config の probing 設定が必要
  • System32 への手動コピーは避け、VC++ ランタイムなどは再頒布パッケージでインストールする
  • 見つからない原因は Process Monitor などで「どこを探したか」を見れば特定できる

結論: exe と同じフォルダに置く

開発環境では動くのに、本番環境に配置すると「DLL が見つからない」エラーになる、というのはよくあるトラブルです。自作 DLL(A.dll)がさらに別の DLL(B.dll)を参照している場合、B.dll はアプリケーションの実行ファイル(.exe)と同じディレクトリに置くのが最も確実で、推奨される方法です。

C:\MyApp\
    MyApp.exe       ← 起動する実行ファイル
    A.dll           ← MyApp.exe が参照する DLL
    B.dll           ← A.dll が参照する DLL(ここに置く)
    MyApp.exe.config

Visual Studio でビルドすると、参照設定で「ローカルにコピー(Copy Local)」が有効な DLL は自動で出力フォルダ(bin\Release など)にコピーされます。本番には出力フォルダの中身を丸ごと配置するのが基本です。

最大の落とし穴: 参照元 DLL のフォルダは探されない

「A.dll が使うのだから、A.dll と同じフォルダに B.dll を置けばよい」と考えがちですが、Windows も .NET も、依存 DLL の検索の基準は参照元の DLL の場所ではなく、プロセス(exe)の場所です。

例えば A.dll と B.dll を C:\Shared\Lib に置き、C:\MyApp\MyApp.exe から A.dll を読み込んだ場合、B.dll は C:\MyApp などから探され、C:\Shared\Lib は探されません。プラグイン形式で DLL を別フォルダから読み込む設計のときに特に起こりやすい問題です。

ネイティブ DLL(C/C++ など)の検索順序

C/C++ で作られたネイティブ DLL や、C# から P/Invoke(DllImport)で呼び出す DLL は、Windows のローダーが次の順で検索します(既定の安全な DLL 検索モードが有効な場合)。

  1. アプリケーションの実行ファイルがあるディレクトリ
  2. システムディレクトリ(C:\Windows\System32)
  3. 16 ビットのシステムディレクトリ(C:\Windows\System)
  4. Windows ディレクトリ(C:\Windows)
  5. カレントディレクトリ
  6. 環境変数 PATH に含まれるディレクトリ

この前に、すでにメモリに読み込まれている DLL や、Windows が管理する既知の DLL(KnownDLLs)が優先されます。

各配置場所の比較

置き場所評価理由
exe と同じフォルダ推奨最初に検索される。アプリごとに独立し、他のアプリへ影響しない
System32 / SysWOW64非推奨管理者権限が必要。システム全体に影響し、同名 DLL の上書き事故(DLL 地獄)の原因になる
PATH に含まれるフォルダ条件付き複数アプリで共有できるが、PATH の順番や他ソフトの同名 DLL で読み込まれる DLL が変わりうる
アプリ固有のフォルダ設計次第アプリ側が SetDllDirectory や AddDllDirectory で検索先を追加している場合のみ有効

64 ビット版 Windows では、System32 が 64 ビット用、SysWOW64 が 32 ビット用です。名前と中身が直感と逆なので注意してください。

.NET アセンブリ(C# の DLL)の場合

C# などで作ったマネージド DLL(アセンブリ)は、ネイティブ DLL とは別の仕組みで探されます。

  • .NET Framework: アプリケーションベース(exe のフォルダ)を探し、厳密名付きのものはグローバルアセンブリキャッシュ(GAC)も参照します
  • .NET(.NET Core / .NET 5 以降): ビルド時に生成される アプリ名.deps.json に依存関係が記録され、通常は exe と同じフォルダから読み込まれます。dotnet publish の出力フォルダを丸ごと配置すれば必要な DLL がそろいます

サブフォルダに DLL をまとめたい場合(.NET Framework)

exe のフォルダを整理するため lib フォルダに DLL を入れたい場合は、app.config(実行時は MyApp.exe.config)で検索先を追加します。指定できるのはアプリケーションベース配下のサブフォルダだけです。

<configuration>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <probing privatePath="lib;plugins" />
    </assemblyBinding>
  </runtime>
</configuration>

よくあるエラーと原因

エラー主な原因
FileNotFoundException: ファイルまたはアセンブリ '…'、またはその依存関係の 1 つが読み込めませんでした.NET の DLL か、その DLL が依存する DLL が exe のフォルダにない
DllNotFoundExceptionP/Invoke で呼ぶネイティブ DLL、またはその依存 DLL が見つからない
BadImageFormatException32 ビットと 64 ビットの不一致(x86 用 DLL を 64 ビットプロセスで読む等)
VCRUNTIME140.dll が見つからない本番機に Visual C++ 再頒布可能パッケージが入っていない

「その依存関係の 1 つ」という文言のとおり、エラーに表示された DLL 自体ではなく、その先の依存 DLL が見つからないケースが多くあります。

どこを探したか確認する方法

  1. 依存関係を一覧する: Visual Studio の開発者コマンドプロンプトで dumpbin /dependents A.dll を実行すると、A.dll が必要とするネイティブ DLL の一覧が出ます
  2. 読み込みの試行を記録する: Sysinternals の Process Monitor で対象プロセスを絞り込み、「NAME NOT FOUND」になっている DLL のパスを確認します
  3. .NET Framework のバインド失敗: アセンブリ バインド ログ ビューアー(fuslogvw.exe)で、どのフォルダを探して失敗したかが分かります
  4. 本番機と開発機で、exe のフォルダの中身(DLL の数・バージョン・ビット数)を比較します

関連

Post Share
子ページ

子ページはありません

同階層のページ
  1. ショートカットキー
  2. dllを参照する方法
  3. エラー一覧
  4. 本番環境のdll内で参照しているdllファイルの置き場所
  5. フォームのタブ切り替え順序を変更する方法
  6. .suoファイルとは