msys2とC++で特定のDLLに依存しないwindowsバイナリを作る

(7/29更新)

背景

msys2上のC++コンパイラwindowsバイナリを作り、エクスプローラから実行すると以下のように実行できないことがある。

f:id:SiunCyclone:20180721174723p:plain
これはエクスプローラ環境変数(PATH)に/mingw64/binが通っていないため起こるが、PATHを通さずとも実行できるようにしたい。

結論

コンパイルオプションに-static -lstdc++ -lgcc -lwinpthreadをつける

試した記録

まず、作成したバイナリがどのようなDLL依存関係になっているか調べた。

使用したコード
#include <windows.h>
#include <iostream>

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
  std::cerr << "Hello" << std::endl;
  return 0;
}
使用したコンパイラ

mingw-w64-x86_64-gcc 7.3.0-2

実行ファイルのDLL依存関係
$ g++ main.cc -o main
$ ldd main
ntdll.dll => /c/Windows/SYSTEM32/ntdll.dll (0x7ffb96eb0000)
KERNEL32.DLL => /c/Windows/System32/KERNEL32.DLL (0x7ffb94750000)
KERNELBASE.dll => /c/Windows/System32/KERNELBASE.dll (0x7ffb932f0000)
msvcrt.dll => /c/Windows/System32/msvcrt.dll (0x7ffb96cc0000)
(*) libstdc++-6.dll => /mingw64/bin/libstdc++-6.dll (0x6fc40000)
USER32.dll => /c/Windows/System32/USER32.dll (0x7ffb961f0000)
win32u.dll => /c/Windows/System32/win32u.dll (0x7ffb93280000)
(*) libwinpthread-1.dll => /mingw64/bin/libwinpthread-1.dll (0x64940000)
GDI32.dll => /c/Windows/System32/GDI32.dll (0x7ffb94810000)
gdi32full.dll => /c/Windows/System32/gdi32full.dll (0x7ffb938b0000)
msvcp_win.dll => /c/Windows/System32/msvcp_win.dll (0x7ffb93810000)
ucrtbase.dll => /c/Windows/System32/ucrtbase.dll (0x7ffb94160000)
(*) libgcc_s_seh-1.dll => /mingw64/bin/libgcc_s_seh-1.dll (0x61440000)

この中で(*)をつけたもの3つが、

  • libstdc++-6.dll
  • libwinpthread-1.dll
  • libgcc_s_seh-1.dll

/mingw64/binにPATHが通っていなければ読み込めないため、この3つを静的リンクすることを目指す。

コンパイルオプションの追加

3つを静的リンクにするために、-static -lstdc++ -lgcc -lwinpthreadオプションをつける。

$ g++ main.cc -o main -static -lstdc++ -lgcc -lwinpthread
$ ldd main
ntdll.dll => /c/Windows/SYSTEM32/ntdll.dll (0x7ffb96eb0000)
KERNEL32.DLL => /c/Windows/System32/KERNEL32.DLL (0x7ffb94750000)
KERNELBASE.dll => /c/Windows/System32/KERNELBASE.dll (0x7ffb932f0000)
msvcrt.dll => /c/Windows/System32/msvcrt.dll (0x7ffb96cc0000)

なお、libgcc_s_seh-1.dllとlibstdc++-6.dllに関しては、それぞれ-static-libgcc-static-libstdc++オプションでも静的リンクにすることができる。

一方、libwinpthread-1.dllは-static-lwinpthreadオプションが存在しない上、-Wl,-Bstatic -lwinpthreadオプションをつけても静的リンクにならない。(謎)

また、下記のように-staticだけをつけた場合、

$ g++ main.cc -o main -static

同じ結果が得られるが、自作のライブラリを動的リンクした時に期待の動作をしなかったため、上の3つのライブラリに関しては毎回指定したほうがよいと思われる。

静的リンクと動的リンクを混ぜる

自作のhogeというDLLをリンクさせたい場合、-L./ -Wl, -Bdynamic -lhogeを追加する。なお、-L././(カレントディレクトリ)をライブラリ検索パスに追加するという意味である。

$ g++ main.cc -o main -static -lstdc++ -lgcc -lwinpthread -L./ -Wl,-Bdynamic -lhoge
$ ldd main
ntdll.dll => /c/Windows/SYSTEM32/ntdll.dll (0x7fffd7820000)
KERNEL32.DLL => /c/Windows/System32/KERNEL32.DLL (0x7fffd6930000)
KERNELBASE.dll => /c/Windows/System32/KERNELBASE.dll (0x7fffd3f50000)
msvcrt.dll => /c/Windows/System32/msvcrt.dll (0x7fffd6b00000)
hoge.dll => /home/siun/pro/wintest/hoge.dll (0x67b00000)

この時注意する点としては、静的リンクを必ず先に書くこと。
下記のように動的リンクを先に書いた場合、静的リンクが正しく行われない。

$ g++ main.cc -o main -L./ -Wl,-Bdynamic -lhoge -static -lstdc++ -lgcc -lwinpthread

また当然ではあるが、このhogeというDLLを作る際にも静的リンクオプションをつける必要がある。

$ g++ hoge.cc -o hoge.dll -static -lstdc++ -lgcc -lwinpthread