ビルドするとerror LNK2001: 外部シンボル “…” は未解決です と出て、exeができません。LNK2019なら「関数 “…” で参照されました」と呼び出し元まで出ますが、LNK2001の行にはそれがありません。その代わり、行頭の .objと二重引用符の中の名前の形から、定義を書き忘れた場所の見当がつきます。
LNK2001とLNK2019の行の違い
宣言だけ書いて定義を書いていないものが5つあるMFCのダイアログ アプリをリンクすると、出力は次のようになります。
StockDlg.obj : error LNK2001: 外部シンボル "protected: void __cdecl CStockDlg::OnBnClickedExport(void)" (?OnBnClickedExport@CStockDlg@@IEAAXXZ) は未解決です
StockDlg.obj : error LNK2019: 未解決の外部シンボル "protected: void __cdecl CStockDlg::LoadStock(void)" (?LoadStock@CStockDlg@@IEAAXXZ) が関数 "protected: virtual int __cdecl CStockDlg::OnInitDialog(void)" (?OnInitDialog@CStockDlg@@MEAAHXZ) で参照されました
StockDlg.obj : error LNK2001: 外部シンボル "public: static class ATL::CStringT<wchar_t,class StrTraitMFC_DLL<wchar_t,class ATL::ChTraitsCRT<wchar_t> > > CStockDlg::s_sLastFolder" (?s_sLastFolder@CStockDlg@@2V?$CStringT@_WV?$StrTraitMFC_DLL@_WV?$ChTraitsCRT@_W@ATL@@@@@ATL@@A) は未解決です
StockExporter.obj : error LNK2001: 外部シンボル "public: virtual int __cdecl CCsvExporter::WriteRow(wchar_t const *,int)" (?WriteRow@CCsvExporter@@UEAAHPEB_WH@Z) は未解決です
SettingsDlg.obj : error LNK2001: 外部シンボル "protected: virtual struct AFX_MSGMAP const * __cdecl CSettingsDlg::GetMessageMap(void)const " (?GetMessageMap@CSettingsDlg@@MEBAPEBUAFX_MSGMAP@@XZ) は未解決です
MyApp.exe : fatal error LNK1120: 5 件の未解決の外部参照
参照元の関数(OnInitDialog)が出ているのは、2行目のLNK2019だけです。LoadStockは、OnInitDialogの中から普通に呼んでいる関数です。残りの4行はLNK2001で、どこから使われているのかは行に書かれていません。
エラーの番号は違っても、意味は同じです。使っている名前の定義が、リンカーが探すライブラリにもオブジェクト ファイルにも無いということです(LNK2001)。宣言はあるのでコンパイルは通り、定義が無いのでリンクで止まります。
番号だけで原因を分けることはできません。3行目のs_sLastFolderはOnInitDialogの中で読み書きしている変数ですが、LNK2001になりました。LNK2019のページでは、staticメンバーが定義されていない場合をLNK2019の例に挙げています。どちらの番号が出ても、その名前の定義がどこにあるか(無いか)を探します。
LNK2001で見るのは行頭の .objと名前の形
参照元の関数が出ない代わりに、行の中には手がかりが2つあります。
1つは行頭の .objです。上の5行では、どれもその名前を使っている側のファイルです。StockDlg.objならStockDlg.cpp、SettingsDlg.objならSettingsDlg.cppの中に、未解決の名前を使っている所があります。もう1つは二重引用符の中の名前です。ソースに書いた形で出るので、先頭にstaticやvirtualが付いているか、関数名がGetMessageMapかを見れば、何を書き忘れたのかの見当がつきます。
上の5行を、この2つと参照元の関数に分けると次のようになります。

| 名前の形 | 書き忘れたもの | 最初に見る場所 |
|---|---|---|
| staticが付いた変数 | staticメンバー変数の定義 | そのクラスの .cppに、クラス名つきの定義が1行あるか |
| virtualが付いた関数 | 仮想関数の定義 | そのクラスの .cppに、関数の本体があるか |
| クラス名::GetMessageMap | BEGIN_MESSAGE_MAPからEND_MESSAGE_MAPまで | 行頭の .objの .cppに、そのクラスのメッセージ マップがあるか |
| staticもvirtualも無い関数 | メッセージ マップに書いたハンドラーの定義など | 行頭の .objの .cppのON_の行と、関数の本体 |
staticメンバー変数の定義が無い
名前の先頭がpublic: staticで、かっこが無いものは、クラスのstaticメンバー変数です。s_sLastFolderの行は型の部分がCStringを展開した長い形で出ますが、名前は最後のCStockDlg::s_sLastFolderです。
クラスの中に書いたstaticの行は宣言で、変数の置き場所はまだできていません。どれか1つの .cppに、変数名の前にクラス名とコロン2つを付けた定義を書きます。
// StockDlg.h(クラスの中。ここは宣言)
static CString s_sLastFolder;
// StockDlg.cpp(どの関数の外側でもよい。ここが定義)
CString CStockDlg::s_sLastFolder;
仮想関数の定義が無い
名前にvirtualが付いている関数は、仮想関数です。CCsvExporter::WriteRowは、派生クラスのヘッダーでオーバーライドを宣言しただけで、本体をどこにも書いていません。
この関数は、CCsvExporterのオブジェクトからは一度も呼んでいませんが、リンクで未解決になります。行頭に出たStockExporter.objには、CCsvExporterのコンストラクターがあります。呼び出しを探しても見つからないときは、そのクラスのオブジェクトを作っている .cppが行頭に出ていないかを見てください。
本体が要るなら、そのクラスの .cppに書きます。オーバーライドが要らなかったのなら、ヘッダーの宣言を消して、基底クラスの関数を使わせます。
// StockExporter.cpp
BOOL CCsvExporter::WriteRow(LPCTSTR lpszName, int nCount)
{
CString sRow;
sRow.Format(_T("\"%s\",%d"), lpszName, nCount);
m_arrRows.Add(sRow);
return TRUE;
}
GetMessageMapが未解決になる:BEGIN_MESSAGE_MAPが無い
自分で書いた覚えの無いGetMessageMapが出てきたら、メッセージ マップの書き忘れです。MFCのヘッダーafxwin.hを見ると、DECLARE_MESSAGE_MAPはGetThisMessageMapとGetMessageMapの2つを宣言するだけのマクロです。2つの本体は、.cpp側に置くBEGIN_MESSAGE_MAPのマクロが書きます。
そのため、ヘッダーにDECLARE_MESSAGE_MAPがあるのに .cppにBEGIN_MESSAGE_MAPが無いと、この行が出ます。クラスをほかのプロジェクトからヘッダーだけ写したときや、ハンドラーが無くなったのでメッセージ マップを丸ごと消したときに起きます。
ハンドラーが1つも無くても、DECLARE_MESSAGE_MAPを残すなら、.cppには空のメッセージ マップを置きます。2番目の引数は基底クラスです。メッセージ マップそのものが要らないなら、ヘッダーのDECLARE_MESSAGE_MAPを消す方法もあります。
// SettingsDlg.cpp
BEGIN_MESSAGE_MAP(CSettingsDlg, CDialogEx)
END_MESSAGE_MAP()
staticもvirtualも無い関数:メッセージ マップの行だけ残っている
1行目のOnBnClickedExportは、staticもvirtualも付いていない普通のメンバー関数ですが、LNK2001で参照元の関数は出ていません。この関数をコードから呼んでいる所は無く、使っているのはメッセージ マップの1行だけです。
BEGIN_MESSAGE_MAP(CStockDlg, CDialogEx)
ON_BN_CLICKED(IDC_BTN_EXPORT, &CStockDlg::OnBnClickedExport)
ON_BN_CLICKED(IDC_BTN_SETTINGS, &CStockDlg::OnBnClickedSettings)
END_MESSAGE_MAP()
afxmsg_.hを見ると、ON_BN_CLICKEDはON_CONTROLを通して、メッセージ マップの表の1行に関数のアドレスを入れるマクロです。関数を呼ぶのではなく、アドレスを表に書いているだけです。ハンドラーの本体を .cppから消して、ON_の行を残したときにこの形になります。
行頭の .obj(ここではStockDlg.obj)の .cppで、メッセージ マップの中を関数名で探します。ボタンが要るならハンドラーの本体を書き、要らないならON_の行とヘッダーのafx_msgの宣言を両方消します。
名前の形で分からないとき
上の4つに当てはまらないときは、名前のクラス名つきの部分(CStockDlg::s_sLastFolderのような所)で、ソリューション全体を検索します。宣言しか見つからなければ定義の書き忘れです。定義が見つかったなら、その .cppがビルドの対象から外れていないか、宣言と引数の型や大文字小文字が食い違っていないかを見ます。
定義の入ったファイルがリンクに渡っているかは /VERBOSEリンカー オプションで、.objや .libの中の名前はDUMPBINの /SYMBOLSで見られます。読み方はLNK1120の記事に書きました。.libの指定漏れやx86とx64の取り違えのように、ライブラリ側が原因のときはLNK1104 / LNK2019 / LNK2001の記事の確認順が使えます。
まとめ
- LNK2001もLNK2019も、定義が見つからないという意味は同じ。LNK2001には参照元の関数が出ない。
- LNK2001では、行頭の .obj(名前を使っている側)と、名前の形を見る。
- staticの変数ならクラス名つきの定義を1行、virtualの関数なら本体を書く。GetMessageMapならBEGIN_MESSAGE_MAPを置く。
- staticもvirtualも無い関数なら、メッセージ マップにON_の行だけ残っていないかを見る。
