リンクエラーを生じさせる方法(Visual Studio)


 Visual Studio 2013 を用いてプログラミングをしているが、リンクエラーを生じさせてしまって、時間を無駄にすることが多い。失敗の記録を示すことで同じ失敗を避けたいと思う。

  • 可能性:文字コードの扱いが一貫していない。
  • 可能性:[C/C++][Code generation][Runtime Library]の扱いが一貫していない。
  • 可能性:リンクするライブラリのディレクトリが指定されていない。
  • 可能性:リンクする設定のlibファイルがまだ用意されていない。
  • 可能性:コピーした設定で間違った依存性・設定になっている可能性
  • 可能性:設定に異なる版のコンパイラがかかわっている可能性

 これらのパターンは、実際にはまったことのあるパターンです。以下これらの可能性について述べます。


可能性:文字コードの扱いが一貫していない。

Visual Studio 2013の場合、文字コードの指定を行わなくてはならない。

[Use Unicode Character Set]
[Use Multi-Byte Character Set]

[All Configurations][Configuration Properties][General] [Project Defaults][Character set]
でどちらか一方を一貫性のあるように設定する。

そうするとvcxprojファイルの中にUnicodeを使う設定が反映される。
(謎:CharacterSetでの設定があるのに_UNICODEマクロがなぜ必要なのか?)

txt:Visual Studio から設定した後のvcxprojの抜粋
<CharacterSet>Unicode</CharacterSet>
<PreprocessorDefinitions>WIN32;_WINDOWS;_DEBUG;_UNICODE;CMAKE_INTDIR="Debug";%(PreprocessorDefinitions)</PreprocessorDefinitions>

推奨:[Use Unicode Character Set]と設定する。
Visual Studio 2013用のCMakeLists.txtの場合、以下の行を追記する。
add_definitions(-D_UNICODE)
この結果vcxprojには次のようにunicodeを使う設定が反映される。

Cmakeによるvcxprojファイルの抜粋
<CharacterSet>Unicode</CharacterSet>    
<PreprocessorDefinitions>WIN32;_WINDOWS;_DEBUG;_UNICODE;CMAKE_INTDIR="Debug";%(PreprocessorDefinitions)</PreprocessorDefinitions>
スクリプトによる文字コード設定の確認

 エディタで複数のファイルをチェックするのは手間なので、スクリプト言語でツールを作ってみた。(例はpythonで書いているけれども、grepコマンドなど好きなものを使えばよい。)

characterSetting.py
# -*- coding: utf-8 -*-
u"""
Visual Studioの設定ファイル*.vxcproj
から文字コードの設定を見るツール。
"""
import glob

for p in glob.glob("*.vcxproj"):
    for line in open(p, "rt"):
        lowerLine=line.lower()
        if lowerLine.find("characterset")>-1 or lowerLine.find("preprocessordefinitions")>-1:
            print p, ":",  line,
実行結果
module.vcxproj :     <CharacterSet>Unicode</CharacterSet>じ
module.vcxproj :     <CharacterSet>Unicode</CharacterSet>
module.vcxproj :       <PreprocessorDefinitions>WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
module.vcxproj :       <PreprocessorDefinitions>WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
test_module.vcxproj :     <CharacterSet>Unicode</CharacterSet>
test_module.vcxproj :     <CharacterSet>Unicode</CharacterSet>
test_module.vcxproj :       <PreprocessorDefinitions>WIN32;_WINDOWS;_DEBUG;_UNICODE;CMAKE_INTDIR="Debug";%(PreprocessorDefinitions)</PreprocessorDefinitions>
test_module.vcxproj :       <PreprocessorDefinitions>WIN32;_WINDOWS;_DEBUG;_UNICODE;CMAKE_INTDIR=\"Debug\";%(PreprocessorDefinitions)</PreprocessorDefinitions>
test_module.vcxproj :       <PreprocessorDefinitions>WIN32;_WINDOWS;NDEBUG;_UNICODE;CMAKE_INTDIR="Release";%(PreprocessorDefinitions)</PreprocessorDefinitions>
test_module.vcxproj :       <PreprocessorDefinitions>WIN32;_WINDOWS;NDEBUG;_UNICODE;CMAKE_INTDIR=\"Release\";%(PreprocessorDefinitions)</PreprocessorDefinitions>
ZERO_CHECK.vcxproj :     <CharacterSet>MultiByte</CharacterSet>
ZERO_CHECK.vcxproj :     <CharacterSet>MultiByte</CharacterSet>

この例では、ZERO_CHECK.vcxprojの文字コード設定が他と違ってMultiByteになっていることがわかる。

可能性:[C/C++][Code generation][Runtime Library]の扱いが一貫していない。

次のエラーメッセージが出る場合には、Runtime Libraryの設定がつじつまがとれていないことを示している。

 error LNK2038: mismatch detected for 'RuntimeLibrary': value 'MTd_StaticDebug' doesn't match value 'MDd_DynamicDebug

自作ライブラリがlibであってDLL化していない場合とか、まずは静的リンクを使う場合を用いてみる。

Releaseモード | Debug モード
静的リンクを使う場合 Multi-threaded (/MT) Multi-threaded Debug (/MTd)
DLLを使う場合 Multi-threaded DLL (/MD) Multi-threaded Debug DLL (/MDd)

これらの設定の意味はMicrosoftサイトに詳しくに書いてある。
/MD、/MT、/LD (ランタイム ライブラリの使用)
[C/C++][Code generation][Runtime Library]から設定する。

vcxprojファイルの一部
Debugモードの部分で
      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>  
Releaseモードの部分で
      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>

>grep RuntimeLibrary *.vcxproj
とすれば、静的リンクなのかDLLによるリンクなのか、DebugモードReleaseモードでの設定が適切になっているかどうかをチェックしやすい。

・可能性:リンクするライブラリのディレクトリが指定されていない。

[VC++ Directories][library Directories]

ヘッダーのディレクトリが与えられてVisual Studio 統合環境中で構文の解釈に成功するようになっても、リンカはその対応するlibファイルの所在など知りようがないから、教えてやる必要があります。
例:Boostのinclude のディレクトリに以下の値だからといって
C:\Program Files\boost_1_58_0
対応するlibのライブラリが
C:\Program Files\boost_1_58_0\stage\lib
であることなど、リンカには知りようがない。

解決方法その1:Visual Studioでプロジェクトの設定を個別に設定する。

そのため、Debug|Releaseのモードの両方に[VC++ Directories][library Directories]の値を設定する。

vcxprojファイルの抜粋
    <IncludePath>C:\Program Files\boost_1_58_0;$(IncludePath)</IncludePath>
    <LibraryPath>C:\Program Files\boost_1_58_0\stage\lib;$(LibraryPath)</LibraryPath>

例:
[Linker][Input][Additional Dependencies]

解決方法その2:pragma comment(lib, "libファイル名")の利用

 C++のソース中で#pragma comment(lib, "libファイル名")を記述します。
この方法はときどき見かけます。
利点:C++のソースコード中に明示的記述できるので、vcxprojファイルを変数するよりも共有しやすいことです。
難点:次のOpenCVの例に見るように、マイナーバージョンまで含めて記述してしまうということ。Debugモードとそれ以外とで記述を変えなくてはならない点です。

#if _DEBUG
#pragma comment(lib, "opencv_core2410d.lib")
#else
#pragma comment(lib, "opencv_core2410.lib")
#endif

CMakeのCMakeLists.txtでは
link_directories()
で追加するリンク対象のライブラリがあるpathを指定する。
.pcxprojファイルの中に次のタグで値が設定される。
txt:Cmakeによるvcxprojファイルの抜粋
<AdditionalIncludeDirectories>C:\Program Files\cppunit-1.12.1\include;C:\Program Files\boost_1_58_0;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
確認中

・可能性:リンクする設定のlibファイルがまだ用意されていない。

 Debugモード or Releaseモード
 文字コード
 MultiThreadの扱い
32bit Or 64bit
 などによって、ライブラリの組み合わせは変わってくる。
 そのため必要な組み合わせのlibが、対応するディレクトリにない場合がある。
 その場合には、対応するlibを追加でビルドする。

例:Boostの場合、
 Boost (http://www.boost.org/)

> b2 --build-type=complete msvc stage

VS 2013 で boost 1.55.0 の 32 / 64 ビット版をビルド
 

・可能性:コピーした設定で間違った依存性・設定になっている可能性

.vcxprojファイルで設定している項目のpathが絶対パスで書かれている場合、
その設定がコピー先の設定ではなく、コピー元のファイルを参照している可能性が高い。そのため、直したつもりの設定と、実際に有効になっているコピー元の設定とがかみ合っていなくて、リンクエラーを生じていることもありうる。
vxcprojのファイルをコピーして用いた場合には、Visual Studioの中でSolution Explorerで疑わしいファイルをいったん外して、適切なファイルを再度含めなおすとよい。

vcxprojファイルをエディタでチェックする

*.vcxprojファイルは、XML形式で書かれたテキストファイルだから、エディタで検索してみること。また、SVNで管理している場合、変更の差分をWinMergeなどを使って表示させると、何がどうなっているかがわかる。
 

可能性:設定に異なる版のコンパイラがかかわっている可能性

error LNK2038: mismatch detected for '_MSC_VER': value というエラーを見たことがあるだろうか?
もし見たことがある場合には、リンクするライブラリを現在使用中のコンパイラのバージョンに対応したものを使うこと。

 Visual StudioのCコンパイラを用いている場合には、Visual Studioのバージョンを常に意識する必要がある。例を挙げれば、
boost_1_58_0\stage\libの下にあるlibファイルはファイル名の一部にlibファイルを生成したコンパイラを示すvc120の記述がある。
libboost_filesystem-vc120-mt-1_58.lib
また、OpenCVの配布の中にあるprecompileのライブラリにも、VCのバージョンごとに異なったファイル構成がある。
C:\opencv_2.4.11\build\x86\vc11\lib
C:\opencv_2.4.11\build\x86\vc12\lib

過去に古い版のVisual Studio を使っていた場合には、古い版のVisual Studio をHDDから削除しておくとよい。
環境変数のPATHなどにも古い版のVisual Studio のディレクトリのpathが入っていたりする。
プロジェクトの設定ファイルの隅から隅までチェックするのが必要なことになってしまっている。
・言語仕様にしたがって書かれたC++のプログラムは、OSが共通であれば、使用するコンパイラやリンカに依存しないのが、期待したいところだが、実際にはそうなっていない。このことは、VisualStudioのバージョンがあがったときには、また、リンカやプリプロセッサの設定に互換性が失われることを意味する。

デバッグモードのlibが見つからないとき

LINK : fatal error LNK1104: cannot open file 'python27_d.lib'
のようなが状況のときには、以下のリンクを参考にしてください。
追記:Debugモードでビルドできないとき

番外編:私家版の改造ライブラリを作ろうとしたときのリンクエラー

「プロシージャ エントリ ポイント ””が *.dllから見つかりませんでした。」

linkした際の情報からすると、該当のdllに所定の関数もしくはメソッドがあると期待されるのにそれが見つからないことを意味するらしい。

"C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\bin\dumpbin.exe" /exports 目的のdllファイル

そうすると、目的のdllファイルのシンボルが表示されるので、どのシンボルでリンクエラーを生じたのかを確認できる。

このときの結果では、dllを生じさせた私家版のソースは、ヘッダファイルと矛盾をきたしていたものだった。
プロシージャ エントリ ポイントの上記のエラーを生じた際には、ヘッダファイルとDLLとの不整合を生じていないか確認しよう。