VC PlusPlus:Link Error LINK2019 未解決のシンボル…で参照されました。 対処方法 新しいページはコチラ
提供: yonewiki
(→概要) |
(→知っていた方が良い小技) |
||
49行: | 49行: | ||
以下のようなコマンドを打つと生成できます。必ずしも出力できるわけではありません。dllを生成したプログラム側で__declspec(dllexport)というような宣言をしてあるものに限ります。 | 以下のようなコマンドを打つと生成できます。必ずしも出力できるわけではありません。dllを生成したプログラム側で__declspec(dllexport)というような宣言をしてあるものに限ります。 | ||
+ | |||
+ | defファイルの中身は自動で適切に生成されることもありますが、以下のような形式に整理しないといけない場合もあります。 | ||
+ | |||
+ | 以下はwintab32.dllっていうファイルから作ったdefファイルの抜粋です。 | ||
+ | <syntaxhighlight2 lang="text"> | ||
+ | Dump of file wintab32.dll | ||
+ | |||
+ | File Type: DLL | ||
+ | |||
+ | Section contains the following exports for Wintab32.dll | ||
+ | |||
+ | 00000000 characteristics | ||
+ | 59BBE69A time date stamp Fri Sep 15 23:41:30 2017 | ||
+ | 0.00 version | ||
+ | 20 ordinal base | ||
+ | 1184 number of functions | ||
+ | 54 number of names | ||
+ | |||
+ | ordinal hint RVA name | ||
+ | |||
+ | 25 0 000079E1 Sync | ||
+ | 22 1 00006235 WTClose | ||
+ | 60 2 000089CC WTConfig | ||
+ | 81 3 000033C3 WTDataGet | ||
+ | (省略) | ||
+ | </syntaxhighlight2> | ||
+ | |||
+ | というように吐き出された時のname欄が関数名でSyncとかWTCloseってのがソレにあたる。関数名には@が入ったりもします。 | ||
+ | |||
+ | <syntaxhighlight2 lang="text"> | ||
+ | LIBRARY (ファイル名(拡張子を省く)) | ||
+ | EXPORTS | ||
+ | (関数名) | ||
+ | (関数名) | ||
+ | (関数名) | ||
+ | (関数名) | ||
+ | </syntaxhighlight2> | ||
+ | |||
+ | コマンドは | ||
<syntaxhighlight2 lang="bash"> | <syntaxhighlight2 lang="bash"> | ||
dumpbin.exe /EXPORT (DLLライブラリファイル名or(完全パスor相対パス)\DLLライブラリファイル名) > (ライブラリ名と同じ名前).def | dumpbin.exe /EXPORT (DLLライブラリファイル名or(完全パスor相対パス)\DLLライブラリファイル名) > (ライブラリ名と同じ名前).def | ||
68行: | 107行: | ||
whereコマンドはオプションによっては特定のフォルダでの検索や、特定のフォルダ+サブフォルダでの検索といった検索もできますが、オプションを指定しない場合は、現在のディレクトリと環境変数のパスの中を検索してくれます。ファイル名にはワイルドカード * や ? が使えます。 | whereコマンドはオプションによっては特定のフォルダでの検索や、特定のフォルダ+サブフォルダでの検索といった検索もできますが、オプションを指定しない場合は、現在のディレクトリと環境変数のパスの中を検索してくれます。ファイル名にはワイルドカード * や ? が使えます。 | ||
+ | |||
+ | |||
+ | 上記検索に加えてさらにもっと正確な検索をします。ライブラリディレクトリと追加のライブラリディレクトリというのをVisualStudioでは設定できるようになっていて、このパスの中に所定のlibファイルがあるかを検索します。ライブラリのディレクトリは、メニューの[プロジェクト]-[プロパティ]から[構成プロパティ]-[VC++ディレクトリ]にある[ライブラリディレクトリ]という設定のパスと[構成プロパティ]-[リンカ]-[追加のライブラリディレクト]に設定されているものです。$(xxx)とかっていうパスの場合は展開する必要がありますので、パス設定を選択して表示される[編集]を選択するとダイアログが起動します。評価された値というのが実際に検索されるパスです。変数では表現されていないので、扱い易いはずです。改行を \n を ; に変換するできるテキストエディタがあると、検索用のパス名を作りやすいです。 | ||
+ | <syntaxhighlight2 lang="bash"> | ||
+ | >where /R "(パス名)" (ファイル名) | ||
+ | パス名\ファイル名 | ||
+ | </syntaxhighlight2> | ||
+ | (パス名)で複数を指定したい場合は ; セミコロンで区切って複数指定することが出来ます。 | ||
+ | |||
+ | |||
+ | パスが通っていない場合、パスを追加すると思いますが、なるべく相対パスを使うと良いでしょう。パスの基点はプロジェクト(*.vcxproj)ファイルの置かれているところからのパスになります。 | ||
=== ''' 対処方法 ''' === | === ''' 対処方法 ''' === | ||
− | '''1.安全ではない古い関数が使われるパターン''' | + | |
+ | ==== '''1.安全ではない古い関数が使われるパターン''' ==== | ||
scanf, printf, strlen, strcmpといったC言語の基本とも言える関数を使ったときに発生します。昨今ではオーバーフローしないような、より安全な関数を使うのが定説になっていますが、勉強のため、ためしにgccとか古いVisual Studioで主に開発しているプログラマからプログラムをもらってきたときとかに発生します。古い関数自体は新しいVisual Studioでもコンパイルできるので、関数は新たにobjファイル化することが出来て問題なく使えます。これがlibファイル化された中で呼び出して、使われている場合です。この場合、他のlibファイルの中で古い関数が提供されている必要があり、リンカ設定をしておかないと駄目です。Visual Studioで最初から設定されているリンカ設定されているlibファイルからは、古い関数が消えています。つまり、参照先が無い。リンクエラーだよ。ってなります。なんで、こんな嫌がらせするのか?と思ってしまいますが、Micorsoftは昔ながらの関数群を標準ヘッダファイルでインライン定義(ヘッダファイルの中に関数の定義をして、ついでにプログラムの内容までヘッダファイルに記述する方法です)して内部で持たせるように変更したかったようです。libファイルによる外部ファイル参照を必要としない方法で扱いたかったんすかね。ナカナカ思い切った互換性を保たない変更です。 | scanf, printf, strlen, strcmpといったC言語の基本とも言える関数を使ったときに発生します。昨今ではオーバーフローしないような、より安全な関数を使うのが定説になっていますが、勉強のため、ためしにgccとか古いVisual Studioで主に開発しているプログラマからプログラムをもらってきたときとかに発生します。古い関数自体は新しいVisual Studioでもコンパイルできるので、関数は新たにobjファイル化することが出来て問題なく使えます。これがlibファイル化された中で呼び出して、使われている場合です。この場合、他のlibファイルの中で古い関数が提供されている必要があり、リンカ設定をしておかないと駄目です。Visual Studioで最初から設定されているリンカ設定されているlibファイルからは、古い関数が消えています。つまり、参照先が無い。リンクエラーだよ。ってなります。なんで、こんな嫌がらせするのか?と思ってしまいますが、Micorsoftは昔ながらの関数群を標準ヘッダファイルでインライン定義(ヘッダファイルの中に関数の定義をして、ついでにプログラムの内容までヘッダファイルに記述する方法です)して内部で持たせるように変更したかったようです。libファイルによる外部ファイル参照を必要としない方法で扱いたかったんすかね。ナカナカ思い切った互換性を保たない変更です。 | ||
83行: | 134行: | ||
− | + | そんな時は旧型対応のライブラリを使いましょう。安全とは言えない方法なので、大きな責任が伴うプログラムではあまりオススメしない方法です。使う人に危害が及んだりする可能性はゼロではないです。位置づけを理解して使ってもらえる範囲なら良いのかもしれません。どんなに頑張っても攻撃される可能性はあるので、世界的な流行りのプログラムでない限りは、深く考える必要はないのかもしれません。少数派のプログラムを狙っても凡人の個人情報ばかりで旨味は少ないですからね。攻撃のヤリガイがないと攻められません。割かしオープンソースでは適当になっているプログラムをよく見かけます。 | |
95行: | 146行: | ||
続きはまた気が向いたら書くので記事は増えていく予定。 | 続きはまた気が向いたら書くので記事は増えていく予定。 | ||
+ | |||
+ | |||
+ | ==== '''2.C言語とC++言語の混在による呼び出し関数の互い違い''' ==== | ||
+ | Visual Studio C++を使っているつもりでも、C言語との互換性は高く、どちらを使っているか意識しなくなりがちですが、C言語で書かれたライブラリをC++の呼び出し規約で呼び出すと関数名が違うというエラーが発生します。呼び出しているライブラリがC言語で書かれた方式なのか?C++言語で書かれた方式なのか?を見極めるには、Dumpbinコマンドで、ライブラリの中の変数名を確認することで解決します。 | ||
+ | |||
+ | <syntaxhighlight2 lang="text"> | ||
+ | dumpbin /LINKERMEMBER (確認したいライブラリの名前).lib | ||
+ | </syntaxhighlight2> | ||
+ | |||
+ | |||
+ | ソースファイルがある場合は、ライブラリを作成するプロジェクトの中のプログラムファイルの拡張子が.cであったり、.cppであったりしています。C++でもCの規約に従ったライブラリを作成することができるので、拡張子だけでは判断しきれないかもしれません。 | ||
+ | |||
+ | |||
+ | で、どう違っているのか?なのですが、C++の場合はライブラリに書き出す関数の名称に名前マングリングという動作が追加されていて、zcallocのような関数はC言語方式だと_zcalloc、C++言語方式だと、?zcalloc@@YAPAXPAXII@Z、というような名前になります。なんじゃソレ? | ||
+ | |||
+ | |||
+ | かなり、わけわからん@@とかYXPAとかどっから来たん?ってなると思います。そもそもマングリングってなんかヤラシイ名前で恥ずかしい(Embarrass)わ。とか言っている日本人のなんと多いことか。そうです。チコちゃんはマングリングが何なのかしっています。 | ||
+ | |||
+ | |||
+ | 修飾~~~っ! | ||
+ | |||
+ | |||
+ | 装飾。英語のmanglingは日本語で修飾という意味です。マングリ返しとは関係ないし、ペロペロしたりもしません。チコちゃんは5歳なのにC++のマングリングまで知っているなんて、マングリ返しでもされたことをあるのかなぁ?「ませてはいませんて」そうなんです。C++では同一の関数名を名前空間を変更することによって定義できます。namespase ですね。だったり、オーバーロードという多重定義によって、引数が違うだけの関数名との切り分けも考慮した命名規則があります。命名規則は別の場所で解説するとして、元の名前がきちんとわかるようにわかりにくく修飾されています。元の名前は以下のようなundnameコマンドで確認できます。 | ||
+ | |||
+ | <syntaxhighlight2 lang="text"> | ||
+ | >undname ?zcalloc@@YAPAXPAXII@Z | ||
+ | Microsoft (R) C++ Name Undecorator | ||
+ | Copyright (C) Microsoft Corporation. All rights reserved. | ||
+ | |||
+ | Undecoration of :- "?zcalloc@@YAPAXPAXII@Z" | ||
+ | is :- "void * __cdecl zcalloc(void *,unsigned int,unsigned int)" | ||
+ | </syntaxhighlight2> | ||
+ | |||
+ | よく、dllを吐き出すプログラムでは、必ずextern "C"(囲われた部分はC言語の扱いで、という方法です。)で定義しましょうとか、言っている教則本やSiteがあったりしますが、名前マングリングの良さを分かっていないか、良さを教えるのを省いているということです。コンパイラによって、修飾規則が違うため、わけのからない技術で相互乗り入れも出来ず、dllの活用の幅が狭くなります。ようするに悪だとしているのだと思います。名前空間や、多重定義を利用するという点でメリットはあります。つまり作ったプログラムが同じコンパイラを使うもの同士だけが使えるdllだってあっていいわけです。なんなら、C言語の呼び出し規約で使いたいなら勝手に名前マングリングの結果を使って呼び出せばええじゃろ。って考え方もあります。これまでに紹介した方法で関数名は掌握できます。とは言ったモノの、Extern "C"を付けないDllは少ないかもしれない。 | ||
+ | |||
+ | |||
+ | ちなみに | ||
+ | |||
+ | Sample.h | ||
+ | <syntaxhighlight2 lang="cpp"> | ||
+ | #ifdef __cplusplus | ||
+ | extern "C" { | ||
+ | #endif | ||
+ | |||
+ | void __cdecl zcalloc(void * vValue, unsigned int uiValue, unsigned int uiValue2); | ||
+ | |||
+ | #ifdef __cplusplus | ||
+ | } | ||
+ | #endif | ||
+ | </syntaxhighlight2> | ||
+ | |||
+ | のように記述することでC++言語を使っている場合にC言語の関数形式に必ず変更するという定義が出来ます。まぁC言語に対応するとか、割かし難しい場合もあります。__cdeclキーワードは、dllに関数を書き出す時に必要になるもので、libファイルにはすべてが書き出されます。 | ||
+ | |||
+ | |||
+ | その場合は、参照するライブラリそのものをC++言語に作り替えるという方法があります。これは流石に面倒です。もう一つは、呼び出している関数を自前で再定義して使うという方法もあります。この場合、C言語で書かれたライブラリのプロジェクトの中にある特定プログラムファイルをSample.cというのをコピーして同じディレクトリにおいて、Sample.cとSample.cppが同じディレクトリに存在する状態になります。Sample.cやSample.cppに変更が発生した場合は両方を修正しなければならなくなります。 | ||
+ | |||
+ | |||
+ | そしてSample.hとSample.cppを自分が開発を進めているプロジェクトに取り込みます。インクルードパスの解決をしている場合は、Sample.hはプロジェクトに追加する必要はないかもしれません。なのでしっかりとSample.cppをプロジェクトに追加します。ヘッダファイルはSample.cpp内で#include "Sample.h"となっているなら、プロジェクトのプロパティのC/C++の項目の全般のところにある追加のインクルードファイルパスにSample.hがあるところを相対パスで指定します。必ずセットで移動する仕組みの関係になっていればです。一緒には動かさない。参照するライブラリ群は常に同じなら絶体パスで指定すると良いでしょう。であれば、本来ならばインクルードは#include <Sample.h>のように記述する方法をとるといいのかもしれません。元のファイルとの関係もあるので、あまり変更しない方がいいと思うので、"Sample.h"のような記述でインクルードするもの有りだとは思います。ちなみに<Sample.h>のようになっていれば、プロジェクトのプロパティのVCディレクトリという欄の追加のインクルードの方にパスを記述します。リンカエラーが何が参照できなかったかというヒントは表示されるので名前マングリング処理されたエラーが表示されても冷静に対処できるはずです。名前マングリングがややこしさを増殖させて感じてしまいがちですが、名前マングリングが起こることが分かっていれば、正確に何がどこに無くてエラーなのかっていうのは探知できると思います。 | ||
+ | |||
+ | |||
+ | C言語からC++言語に変更するには、ファイルの拡張子の変更だけで済む場合もありますが、関数の引数の型定義が、外出しになっている以下のように記述された関数 | ||
+ | |||
+ | Sample.c | ||
+ | <syntaxhighlight2 lang="c"> | ||
+ | |||
+ | void zcalloc(vValue, uiValue, uiValue2) | ||
+ | void * vValue, | ||
+ | unsigned int uiValue, | ||
+ | unsigned int uiValue2 | ||
+ | { | ||
+ | … | ||
+ | } | ||
+ | </syntaxhighlight2> | ||
+ | を | ||
+ | |||
+ | Sample.cpp | ||
+ | <syntaxhighlight2 lang="cpp"> | ||
+ | |||
+ | void zcalloc(void * vValue, unsigned int uiValue, unsigned int uiValue2) | ||
+ | { | ||
+ | … | ||
+ | } | ||
+ | </syntaxhighlight2> | ||
+ | と、こんな感じに修正する必要があります。メンドクサイ。 | ||
+ | |||
+ | |||
+ | このような不具合は同じプロジェクト内でlibを参照する場合でも同じです。プロジェクト内で半分引用したりした場合は、それ以外のコードを元々のライブラリを参照するようになっていて、半分引用してプロジェクト内では、Cの規約で呼び出されるように関係性が保たれていて引用したプロジェクトは問題なくビルド出来ます。しかし、そのプロジェクトに依存している中心的役割のプロジェクトからはC++の規約で引用したプロジェクトのlibを参照するようになっていれば、さらに引用元の関数を参照する際に、リンクエラーが発生します。さっきまでうまく行ってたのに依存してる関数だけリンクエラーとか意味わかんねぇっていう気分になります。実にややこしいことです。 | ||
+ | |||
+ | |||
+ | これが、C++とCの混合によるリンクエラーの仕組みです。要するに?なんとかして合わせろ!っていう事しか言えません。しかもC言語をC++言語に書き直したりする道を選んだときは、C言語独自に許された記法もあるので、割かしメンドクサイ変更をしつつ、あたらしく作ったCppファイルをメンテしないと駄目なこともあります。しっかりライブラリの設定をしたのにリンクエラーがでるときは、たぶん、こういう感じのリンクエラーが一番多いと思います。 | ||
[[VC PlusPlus]]に戻る | [[VC PlusPlus]]に戻る |