ぷるぷるの雑記

低レイヤーがんばるぞいなブログ. 記事のご利用は自己責任で.

Windowsでどうしてもリンクできないとき

Window用のC/C++プログラムのビルド時にどうしてもリンクできないときは以下の確認をしましょう

1. verboseオプション

ビルド時に問題が発生した場合はコンパイラとリンカにverboseオプションをつけて詳細を出力させましょう. 注意点としては、たいがいのビルドはmakeかmsbuild(Visual Studio)かgccによってされるので、そのままverboseオプションをつけても(makeやmsbuildへのオプションとみなされ)リンカにverboseオプションを渡すことが出来ないことです.

ビルドにmakeやmsbuild(Visual Studio)を使っている場合は、IDEのプロジェクションオプションからコンパイラとリンカにverboseオプションをつけましょう. 何かしらのIDEを使ってビルドした場合、たいていログが出力されてリるのでそれを見てもよいと思います. gccの場合は一部のオプションを除き普通にオプションをつけただけではコンパイラにオプションを渡すことになります. リンカにオプションを渡したい場合は「-Wl,--verbose」のようにオプションの前に「-Wl,」をつけましょう.

2. ライブラリディレクトリの設定

リンクしたいライブラリがへのパスがオプションで確実に指定されているか確認しましょう. リンカのverboseオプションが有効になっているなら大体コンソールに表示されると思います. オプションの文字は処理系によって異なるので注意.

3. ライブラリファイルの設定

リンクしたいライブラリが確実にリンカの引数に指定されているか確認しましょう. オプションの文字は処理系によって異なるので注意.

4. シンボル情報

Windowsの場合、静的ライブラリとインポートライブラリという異なる概念がどちらも.libという拡張子で表されるという凶悪仕様があります. インポートライブラリとは、動的ライブラリのリンクに用いるファイルです. だいたいの静的ライブラリはファイル名がlibから始まりますが、そうでない静的ライブラリも普通に存在します . 一方、インポートライブラリ中のシンボルには__imp__から始まるという規則があります. なので、本当に欲しいシンボルが_symbolName(静的ライブラリ)であるにもかかわらず、ライブラリ内のシンボルが__imp__symbolName(インポートライブラリ)になっていないか(あるいはその逆になっていないか)確認しましょう.

バイナリツールには通常オブジェクトファイル内のシンボル情報を表示するコマンドがついてきます. MinGWLinux(Cygwin)の場合はnmというコマンド、Visual Studio(MSBuild)の場合はdumpbinというコマンドが利用できます. 適当なバイナリファイルを引数にすると次のようにシンボル情報が表示されます.

// cygwinのnmコマンドでシンボル情報表示

C:\> cygwin64\bin\nm C:\cygwin64\lib\crt0.o
0000000000000000 b .bss
0000000000000000 d .data
0000000000000000 N .debug_abbrev
0000000000000000 N .debug_aranges
0000000000000000 N .debug_frame
0000000000000000 N .debug_info
0000000000000000 N .debug_line
0000000000000000 N .debug_line_str
0000000000000000 N .debug_str
0000000000000000 p .pdata
0000000000000000 r .rdata$zzz
0000000000000000 t .text
0000000000000000 r .xdata
0000000000000000 T WinMainCRTStartup
                 U cygwin_crt0
                 U cygwin_premain0
                 U cygwin_premain1
                 U cygwin_premain2
                 U cygwin_premain3
                 U main
0000000000000000 T mainCRTStartup
// MSBuild(Visual Studio)のdumpbinでシンボル情報表示

C:\> "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.16.27023\bin\Hostx64\x64\dumpbin" /LINKERMEMBER "C:\Program Files (x86)\Windows Kits\10\Lib\10.0.22621.0\ucrt\x64\ucrt.lib"

中略
    187F6 _cgets
    187F6 __imp__cgets
    1886E _cgets_s
    1886E __imp__cgets_s
    188E8 _cgetws
    188E8 __imp__cgetws
    18960 _cgetws_s
    18960 __imp__cgetws_s
    189DA _cputs
    189DA __imp__cputs
    18A52 _cputws
    18A52 __imp__cputws
    18ACA _getch
    18ACA __imp__getch
    18B42 _getch_nolock
    18B42 __imp__getch_nolock
    18BC0 _getche
    18BC0 __imp__getche
    18C38 _getche_nolock
    18C38 __imp__getche_nolock
    18CB8 _getwch
    18CB8 __imp__getwch
中略

MinGWによって提供されるオブジェクトファイルのシンボル情報をMinGWのnmコマンドとMSBuild(Visual Studio)のdumpbinコマンドで表示してみました. 後者を見ると分かるように、静的リンク用のシンボルと動的リンク用のシンボルの両方を含む(静的ライブラリでもありインポートファイルでもある).libファイルもあることが分かります. あるいは、このようなファイルを作りたかったので両者の拡張子をわざと同じにしているのかもしれません.

さて、以降はdumpbinだけを考えます. ライブラリによっては動的ライブラリと静的ライブラリの両方を提供しているライブラリがあります. これは一見喜ばしいことのように思えますが、言い換えると そのライブラリを使用するプロジェクトのビルドオプションで、動的ライブラリと静的ライブラリのどちらを利用するか設定しなければならないということです.

例えば、GLEWというライブラリは動的ライブラリと静的ライブラリの両方の形式が提供されています. ヘッダファイルの一部を見ると、プリプロセスのマクロ定数によって__declspecをつけたりつけなかったりしています. このようにしてGLEWを使うプロジェクト(=GLEWのヘッダをインクルードするプロジェクト)は、プリプロセスのマクロ定義によってGLEWを動的ライブラリとしてリンクするか静的ライブラリとしてリンクするかを切り替えることが出来ます. IDEのプロジェクトプロパティや、CMake時にプリプロセッサのマクロ定義を間違えないようにしましょう.

// glew.h

/*
 * GLEW_STATIC is defined for static library.
 * GLEW_BUILD  is defined for building the DLL library.
 */

#ifdef GLEW_STATIC
#  define GLEWAPI extern
#else
#  ifdef GLEW_BUILD
#    define GLEWAPI extern __declspec(dllexport)
#  else
#    define GLEWAPI extern __declspec(dllimport)
#  endif
#endif

5.そもそも類似名のシンボルも存在しないとき

nmやdumpbinコマンドを使って.libのシンボルを表示したとき、似たシンボル名が全くない場合はそもそも必要なライブラリが足りていない可能性があります. あるいは、リンクしようとしているライブラリのバージョンが古くシンボルが存在しないか、バージョンが新しくなった時に削除された可能性があります. 適切なライブラリがそもそも存在するかを確認しましょう.

参考

learn.microsoft.com

stackoverflow.com