2019年1月1日火曜日

Visual Studioでのリンカエラー解決方法

Visual C++でプログラムをビルドしていて次のようなエラーはリンカが出力したエラーメッセージ. これまでは,このエラーが出ると,もうどうしていいか分からなくて人生終わりみたい感じてしまってたけど,最近ようやく対処法が分かってきたのでまとめとく.

Test.obj : error LNK2001: 外部シンボル ""__declspec(dllimport) public: __cdecl google::LogMessageVoidify::LogMessageVoidify(void)" (__imp_??0LogMessageVoidify@google@@QEAA@XZ)" は未解決です。

ちなみにこのエラーはglogというGoogleが開発したログ出力用ライブラリを用いたC++のプロジェクトをビルドする際に表示されたエラー.

エラーの意味を確認する

error LNK2001をググると例えばこんな文章が出てくる. この文章を読んで意味が分かる人は,たぶんこの記事を読む必要はない. 正確に理解しようとすると話が長く難しくなって意味不明になるので,何がエラーの原因なのかを推測するときの自分の連想のしかたを,ざっくりとメモ.

  1. 「Test.obj」ってのは,たぶんtest.cppとかtest.cとかをコンパイルしたときにできる中間生成ファイル. リンカのエラーだからコンパイルは通ってるってことで,たぶんTest.cppなどのソースコードにエラーはない. DLLが見つからないというエラーなら実行時に出るのでそれでもない.
  2. 「error LNK2001」ってのは,test.cppとかtest.cをコンパイルして出来た中間生成ファイルであるobjファイル(Test.obj)から 呼び出されている関数が見つからないというエラー.
  3. 「外部シンボル」は要するに関数名のこと. それがTest.objから呼び出されてるけど,リンカに入力されたlibファイルなどの中にはそんな関数無いという意味.
  4. 「__declspec(dllimport) public: __cdecl google::LogMessageVoidify::LogMessageVoidify(void)」はその無いと言ってる関数の名前. 長すぎて意味不明だけど「LogMessageVoidify::LogMessageVoidify」という文字があるからLogMessageVoidifyクラスの コンストラクタLogMessageVoidify()が見つからないっていってるんだろうなと想像する. ここで「__declspec(dllimport)」はDLL内の関数という意味.ただし,DLL化された関数でもコンパイル時にエラーにならないように, DLLとlibはセットになっていて,libにはヘッダファイル内の関数プロトタイプのような,入出力の変数の型と関数名,名前空間だけが書かれている.
  5. つまり,たぶん何かのlibファイルが足らないんだろうから「LogMessageVoidify lib」などとググってみて, 必要なlibファイルを探す.今回の場合は,glogというライブラリglog.libが足らないということが分かった.

ちなみに,この「?」や「@」を含む関数名へんてこなの表記は名前の装飾と呼ばれていおりundnameコマンドで元の関数の表記に戻すことができるらしい.この表記でも元の関数が想像できるのでundnameは使ったことがない.

ライブラリの指定の有無とパスを確認する

ここで次の一手はlibファイル(ここではglog.lib)を正しくリンカに渡せているのか確認したいところだけど, glog.libというファイル自体が見つからないということだったらこのようなエラーがでるはずなので今回は違う. そもそもglog.libをリンクせよという指定が正しくできていないか,そのような指定はできているがglog.libの中にLogMessageVoidifyという関数が見つからないのかどちらかということになる. まずは,glog.libをリンクしろという指定が正しくないのではと疑ってみる.

LINK : fatal error LNK1104: ファイル 'glog.lib' を開くことができません。

#pragmaを使う

そもそも必要なライブラリがglog.libであるということをリンカに伝えられていないということだから, ソースコード内にこのようなマクロを追加すると解消されるかもしれない.

#pragma comment(lib,"glog.lib")

ソースコードは直接触りたくないなら次のVisual Studioの設定を確かめてみる.

Visual Studioの設定を確認する

  1. 「プロジェクト」メニューをクリック
  2. プロパティをクリック
  3. 左の「構成プロパティ」内の「リンカー」を選択
  4. 「入力」項目を選択
  5. 「追加の依存ファイル」内にglog.libがないか確認

ここにライブラリ名glog.libを追加すればたいてい問題は解決するか, 上記のファイルが見つからないというエラーに変わるので「リンカー」→「全般」→「追加のライブラリディレクトリ」にlibファイルのあるディレクトリへのパスを設定すればいい.

msbuildコマンドでリンカに渡されるオプションを確認する

Visual Studioはライブラリの設定手段が複数ありややこしい.結局,どのライブラリがリンカに渡されたのか結果を知りたいところ. そんなときに便利なのは,コマンドラインからプロジェクトをビルドする方法(Visual StudioのGUIから確認する方法があるかもしれないけど知らない). 例えば,ALL_BUILD.vcxprojというプロジェクトをビルドする場合, このファイルがあるフォルダまでカレントディレクトリを移動して, 次のコマンドをたたけばいい.

msbuild ALL_BUILD.vcxproj /p:Configuration=Release

このコマンドは,すでにVisual StudioのGUIでプロジェクトを作成していても問題なく実行できる. そうすると次のようにリンカに渡されるライブラリ名(*.lib)やオブジェクトファイル名(*.obj)が確認できる.

Link:
  C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.16.27023\bin\HostX86\x64\link.exe /ERRORREPORT:QUEUE /OUT:"C:\Library\ceres-solver\bld\bin\Release\simple_bundle_adjuster.exe" /INCREMENTAL:NO /NOLOGO ..\lib\Release\ceres.lib C:\Library\glog\bld\Release\glog.lib dbghelp.lib c:\library\suitesparse\inst\lib64\lapack_blas_windows\liblapack.lib c:\library\suitesparse\inst\lib64\lapack_blas_windows\libblas.lib kernel32.lib user32.lib gdi32.lib winspool.lib shell32.lib ole32.lib oleaut32.lib uuid.lib comdlg32.lib advapi32.lib /MANIFEST /MANIFESTUAC:"level='asInvoker' uiAccess='false'" /manifest:embed /PDB:"C:/Library/ceres-solver/bld/bin/Release/simple_bundle_adjuster.pdb" /SUBSYSTEM:CONSOLE /TLBID:1 /DYNAMICBASE /NXCOMPAT /IMPLIB:"C:/Library/ceres-solver/bld/lib/Release/simple_bundle_adjuster.lib" /machine:x64 /ignore:4049 simple_bundle_adjuster.dir\Release\simple_bundle_adjuster.obj

この場合では,C:\Library\glog\bld\Release\glog.libがリンカの入力に指定されていることが分かる. kernel32.libのような基本的なライブラリもここに表示されているということは, ここになければ目的のlibファイルがリンカに渡されていないということになる(たぶん).

dumpbinでlibファイルの中身を確認する

これまでの方法で確かにlibファイルは存在しリンカの引数に正しく渡されているのに, それでも最初のエラーが消えない場合は,そもそもそのlibファイルの中に目的の関数(今回でいえばLogMessageVoidify::LogMessageVoidify)が入っていない可能性もある. libファイルの中身を確認するにはlibファイルがある場所にカレントディレクトリを移して次のコマンドを打てばいい.

dumpbin /LINKERMEMBER glog.lib > glog.txt

これで次のような内容のテキストファイル(glog.txt)が生成される.

Microsoft (R) COFF/PE Dumper Version 14.00.24215.1
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file C:\Library\boost_1_60_0\lib\libboost_atomic-vc140-mt-1_60.lib

File Type: LIBRARY

Archive member name at 8: /               
568FB871 time/date Fri Jan  8 22:24:01 2016
         uid
         gid
       0 mode
     53D size
correct header end

    16 public symbols

      B74 ??0scoped_lock@lockpool@detail@atomics@boost@@QAE@PDX@Z
      B74 ??1scoped_lock@lockpool@detail@atomics@boost@@QAE@XZ
      B74 ?clear@?$msvc_x86_operations@EU?$operations@$00$0A@@detail@atomics@boost@@@detail@atomics@boost@@SAXACEW4memory_order@4@@Z
      B74 ?exchange@?$operations@$00$0A@@detail@atomics@boost@@SAEACEEW4memory_order@4@@Z

(つづく)

このファイル内に目的の関数名が含まれるかどうか検索すればいい. ただし,関数名は同じでも引数などが異なる場合に注意.

ソースコード確認する

上記の方法でlibファイルを中身を覗いてもその中に目的の関数がなかった場合. ライブラリ自体のコンパイル時にその関数がエクスポート(外から参照できる形で定義)されていない可能性がある. その場合はもうライブラリのソースを修正するしかない(?).

今回glogを例に上げているが実際にglogではインライン化された関数がエクスポートされていなかった. logging.hには以下のコードが書かれており,インライン関数はインライン展開された場合,__declspec(dllimport)が指定されていてもlibファイル内に関数名が残らない場合があるらしい.

#   define GOOGLE_GLOG_DLL_DECL  __declspec(dllimport)
class GOOGLE_GLOG_DLL_DECL LogMessageVoidify {
 public:
  LogMessageVoidify() { }

この関数は少し特別であえて何もしない関数が定義されている.何もしないけど,コンパイラには認識させてプログラムの行番号やファイル名などをマクロを正しく表示させたいという意図なのかな. そこでヘッダ内で関数定義を完結させずに,ヘッダ内はプロトタイプのみにして,cppファイル内に何もしない関数を定義する.

.hファイル内の定義から「{}」を削除して,関数プロトタイプ宣言のみにする.

class GOOGLE_GLOG_DLL_DECL LogMessageVoidify {
 public:
  LogMessageVoidify();

.cppファイルには関数本体を記述する.

LogMessageVoidify::LogMessageVoidify(){}

このソースコードの修正でようやくビルド時のリンカエラーが消えた.

まとめ

まとめるとリンカエラーに対しては次の手順でエラーの原因を確認したらいい.

  1. エラーの意味を確認する
  2. libファイル指定の有無を確認
  3. ライブラリファイルへのパスを確認
  4. libファイル内の関数名を確認
  5. libファイルのソースを確認する

参考