【C++】「DLLが見つかりません」で起動できない原因とは?|0xC0000135・0xC000007B・0xC0000139をわざと再現して直し方を整理

【C++】「DLLが見つかりません」で起動できない原因とは?|0xC0000135・0xC000007B・0xC0000139をわざと再現して直し方を整理 C++

自分のPCでは動くexeを友達に渡したら、「〇〇.dllが見つからないため、コードの実行を続行できません」と言われて、起動しませんでした。別のときには、0xc000007bという、意味の分からない番号だけが出ます。

exeが起動する前に止まる「DLLが見つかりません」系のエラーには、よくあるものだけでも3種類あり、ダイアログの文面も終了コードも、それぞれ違います。そこで、3種類をわざと起こして、実際に出たダイアログと終了コードを並べました。調べるときに使うdumpbinの使い方も、実物で確認しています。

検証は、Visual Studio 2022付属のMSVC(19.44)で、自作のDLLとそれを使うexeを作って行いました。

DLLが見つからないとは?

DLLとは、

・実行ファイル(exe)が、起動するときに別のファイルとして読み込む部品。関数の実体が、exeではなくDLLの中にある

です。DLLを使うexeは、起動した瞬間にOSが「必要なDLLをすべて探して読み込む」ことから始めます。ここで1つでも失敗すると、main関数が呼ばれる前に、起動そのものが止まります。だから、デバッガでmainの先頭にブレークポイントを置いても、そこまで届かないのです。

まず、実験用に作ったDLLと、それを使うexeです。

▼mylib.cpp(新しい版のDLL)
// mylib.dll(新しい版): Add と Add2 を公開する
extern "C" __declspec(dllexport) int Add(int a, int b) { return a + b; }
extern "C" __declspec(dllexport) int Add2(int a, int b) { return (a + b) * 2; }
▼main.cpp(このDLLを使うexe)
#include <cstdio>

extern "C" __declspec(dllimport) int Add(int a, int b);   // mylib.dll の関数

int main() {
    std::printf("Add(1, 2) = %d\n", Add(1, 2));
    return 0;
}
cl /LD mylib.cpp                  ← DLL(mylib.dll)と、インポートライブラリ(mylib.lib)を作る
cl main.cpp mylib.lib              ← exeを作る

「このexeが、どのDLLを必要としているか」は、dumpbin /dependentsで確認できます。

  Image has the following dependencies:

    mylib.dll
    KERNEL32.dll

mylib.dllが、必要なDLLの一覧に入っています(KERNEL32.dllは、Windowsにもとから入っているDLLです)。exeを他の場所に持っていくときは、この一覧にあるDLLも一緒に持っていく必要があります。

3種類の症状を、実際に起こす

同じexeでも、DLLの状態によって、症状は3通りに分かれました。

実際に出た3種類のエラーダイアログ。DLLが見つからない、32/64bitが合っていない、関数が見つからない
実際に出たダイアログと、プロセスの終了コード
症状終了コード(符号付きで見ると)ダイアログの文面(要点)
①DLLが無い0xC0000135-1073741515mylib.dllが見つからないため、コードの実行を続行できません
②32bit/64bitが合っていない0xC000007B-1073741701アプリケーションを正しく起動できませんでした(0xc000007b)
③DLLの版が違う0xC0000139-1073741511プロシージャ エントリ ポイントAdd2が…見つかりませんでした
「符号付き」は、コマンドプロンプトの%errorlevel%などで、負の数として表示される値

①DLLが見つからない(0xC0000135)

いちばんよくある症状です。DLLをexeの隣に置いていないだけで起こります。ダイアログには、見つからないDLLの名前が書かれています。

直し方を3通り試しました。DLLの置き場所を変えて、同じexeを起動した結果です。

[A] DLLを、実行ファイルと同じフォルダに置く
    結果: Add(1, 2) = 3
    終了コード: 0x00000000
[B] 作業フォルダにだけDLLがある(実行ファイルのフォルダには無い)
    結果: Add(1, 2) = 3
    終了コード: 0x00000000
[C] PATHの通ったフォルダにだけDLLがある
    結果: Add(1, 2) = 3
    終了コード: 0x00000000
  • [A]実行ファイルと同じフォルダ:動く。いちばん確実で、配布するときの基本
  • [B]作業フォルダにだけDLLがある:動いた。ただし、実行ファイルがあるフォルダとは別のフォルダから起動した場合の話。作業フォルダは、起動のしかた(ショートカット、Visual Studioのデバッグ実行など)で変わるので、「自分の環境では動く」の原因になりやすい
  • [C]PATHの通ったフォルダ:動いた。ただし、他のPCに渡す配布では、相手のPCのPATHに頼れない

[B]は、特に罠です。Visual Studioからのデバッグ実行では動いたのに、できたexeをダブルクリックしたら「DLLが見つからない」というのは、「作業フォルダ」と「実行ファイルのフォルダ」が違うことで起こりえます。配布するときは、必ず[A]の形(exeとDLLを同じフォルダに)にします。

②32bit/64bitが合っていない(0xC000007B)

exeは64bit、DLLは32bit、という組み合わせです。DLLは「ある」のに、起動できません。ダイアログは、どのDLLが原因か、書いていません。

DLLが何bit用かは、dumpbin /headersで確認できます。

x86\mylib.dll : 14C machine (x86)
full\mylib.dll : 8664 machine (x64)

14Cが32bit(x86)、8664が64bit(x64)です。exeとDLLが同じ種類であることを確認します。直し方は、exeかDLLのどちらかを、もう一方に合わせて作り直すことです。同じ名前の32bit版と64bit版のDLLが、別のフォルダにあるときに、取り違えて起こしやすい症状です。

③DLLの版が違う(0xC0000139)

DLLは見つかり、bitも合っています。でも、exeが使おうとしている関数が、そのDLLに入っていないパターンです。exeを作ったときの新しいDLLを使い、実行するときは古い版のDLLが置いてあった、という状況を再現しました。

▼main.cpp(新しい版にだけある関数Add2を使うexe)
#include <cstdio>

extern "C" __declspec(dllimport) int Add2(int a, int b);  // 新しい版にだけある関数

int main() {
    std::printf("Add2(1, 2) = %d\n", Add2(1, 2));
    return 0;
}

新しい版のDLLと、古い版のDLLが公開している関数を比べます。

    ordinal hint RVA      name
          1    0 00001000 Add
          2    1 00001020 Add2
    ordinal hint RVA      name
          1    0 00001000 Add

古い版にはAddしか無く、Add2がありません。そのため、ダイアログには「プロシージャ エントリ ポイントAdd2が…見つかりませんでした」と出ました。ここで関数名が書かれているのが、①②との違いで、DLLの版違いを疑う手がかりになります。ただし、そのあとに「ダイナミック リンク ライブラリ」として表示されているのは、DLLではなく、exeのパスでした(上の画像)。名前だけ見ると紛らわしいので、注意が必要です。

【重要】罠:ダイアログが出ないと、終了コードしか手がかりがない

実は、実験の最初の何回かは、ダイアログがまったく出ませんでした。DLLが見つからなくても、画面には何も出ず、プロセスが終了コードだけを残して消えたのです。

subprocess.Popen(["main.exe"])  → 画面には何も出ない
終了コード: 0xC0000135

エラーダイアログを出さない設定(SetErrorMode)で動いている親プロセスから起動すると、子プロセスにもその設定が引き継がれて、ダイアログが出なくなることがあります。ゲームのランチャーや、スクリプト、サービスから起動したexeで、「何も起きずに終わった」ようなときは、終了コード(%errorlevel%)を確認します。

main.exe
echo %errorlevel%

-1073741515と出たら、①(DLLが無い)です。上の表と照らし合わせて、原因を絞ります。

調べ方の手順

  1. 終了コードを見る:0xC0000135(DLLが無い)/0xC000007B(bitの不一致)/0xC0000139(関数が無い)
  2. dumpbin /dependentsで、exeが必要としているDLLの一覧を確認する
  3. DLLの場所を確認する(exeと同じフォルダに、一覧のDLLがあるか)
  4. dumpbin /headersで、exeとDLLのbitをそろえる
  5. dumpbin /exportsで、DLLが必要な関数を公開しているか確認する
dumpbin /dependents main.exe
dumpbin /headers mylib.dll | findstr machine
dumpbin /exports mylib.dll

dumpbinは、Visual Studioに付属しています。「x64 Native Tools コマンド プロンプト」を開くと、パスが通った状態で使えます。

DLLが見つからないときのよくある失敗例と対処法

①自分のPCで動いたので、そのまま配布する

自分のPCでは、PATHや作業フォルダ、すでにインストール済みのDLLのおかげで動いていることがあります。配布する前に、DLLを入れていない別のフォルダに、exeを1つだけコピーして起動してみると、必要なDLLが分かります。

②作業フォルダにDLLがあるから動いていた

[B]のとおり、作業フォルダにあるDLLは見つかります。ただし、起動のしかたで作業フォルダが変わると、見つからなくなります。exeの隣にDLLを置くのが、いちばん確実です。

③32bit版と64bit版のDLLを取り違える

ビルドの構成(x86/x64)を切り替えたのに、DLLのほうを切り替え忘れると、0xC000007Bで落ちます。dumpbin /headersで、machineを確認します。

注意点

  • DLLの問題は、mainが呼ばれる前に起こる。デバッガのブレークポイントには届かない
  • 終了コードで、原因の種類が分かる(0xC0000135・0xC000007B・0xC0000139)
  • 0xC000007Bは、DLLが「ある」のに起こる。bitの不一致を疑う。ダイアログにDLL名は出ない
  • 0xC0000139は、関数が無い(版違い)。ダイアログの「ライブラリ」欄は、exeのパスが出た
  • エラーダイアログが出ない環境では、終了コードだけが手がかりになる
  • 配布するときは、exeと必要なDLLを同じフォルダに置く
  • 今回の検証は、Windows 11(日本語環境)・MSVC 19.44。ダイアログの文面は、OSの版や言語で変わる

まとめ

  • DLL絡みの起動失敗は、3種類。終了コードとダイアログの文面が、それぞれ違う
  • dumpbin /dependentsで必要なDLL、/headersでbit、/exportsで関数を調べる
  • DLLは、exeと同じフォルダに置くのが確実。作業フォルダやPATHでも見つかるが、起動のしかたに左右される
  • ダイアログが出ないときは、%errorlevel%を見る

「DLLが見つかりません」は、見た目は1つのエラーですが、実際は3種類あり、調べる場所が違います。ダイアログの文面と終了コードを見れば、どこを調べるかが決まります。次に起動しなかったら、まず終了コードを見てみてください。

関連記事