← All guides

Build errors

LNK2001 Unresolved External Symbol in MFC: Find the Missing Definition When No Caller Is Shown

Published

Your MFC project compiles, but the link step fails with error LNK2001: unresolved external symbol "..." and no EXE is produced. With LNK2019 the line ends with referenced in function "...", so you can go straight to the caller. An LNK2001 line has no such part. It still tells you two things: the object file at the start of the line, and the full name of the symbol in quotes. Together they are usually enough to find the definition that is missing.

LNK2001 and LNK2019 lines side by side

This MFC dialog app has five members that are declared but never defined. Linking it gives this output:

StockDlg.obj : error LNK2001: unresolved external symbol "protected: void __cdecl CStockDlg::OnBnClickedExport(void)" (?OnBnClickedExport@CStockDlg@@IEAAXXZ)
StockDlg.obj : error LNK2019: unresolved external symbol "protected: void __cdecl CStockDlg::LoadStock(void)" (?LoadStock@CStockDlg@@IEAAXXZ) referenced in function "protected: virtual int __cdecl CStockDlg::OnInitDialog(void)" (?OnInitDialog@CStockDlg@@MEAAHXZ)
StockDlg.obj : error LNK2001: unresolved external symbol "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: unresolved external symbol "public: virtual int __cdecl CCsvExporter::WriteRow(wchar_t const *,int)" (?WriteRow@CCsvExporter@@UEAAHPEB_WH@Z)
SettingsDlg.obj : error LNK2001: unresolved external symbol "protected: virtual struct AFX_MSGMAP const * __cdecl CSettingsDlg::GetMessageMap(void)const " (?GetMessageMap@CSettingsDlg@@MEBAPEBUAFX_MSGMAP@@XZ)
StockList.exe : fatal error LNK1120: 5 unresolved externals

Only the second line, the LNK2019, names a caller: LoadStock is an ordinary function called from OnInitDialog. The other four are LNK2001 and say nothing about where the symbol is used.

The two errors mean the same thing. Code refers to a symbol, and the linker cannot find its definition in any object file or library it searched (LNK2001). The declaration is enough for the compiler, so every .cpp file compiles, and the build stops at the link step.

The error number does not tell you the cause, either. s_sLastFolder is read and assigned in OnInitDialog, yet it came out as LNK2001, while Microsoft’s LNK2019 page uses an undefined static member as an LNK2019 example. Whichever number you get, search for where that name is defined, or confirm that it is not defined anywhere.

What an LNK2001 line tells you

The object file at the start of the line is the one that uses the symbol. StockDlg.obj comes from StockDlg.cpp, and SettingsDlg.obj from SettingsDlg.cpp, so that .cpp file contains the reference.

The quoted name is the symbol as it appears in source, with its access level, static or virtual, return type, and class. The part in parentheses after it is the decorated name the linker actually looks for. You can read the error without decoding it.

For the four LNK2001 lines above, this is what each kind of name means and which file to open:

The name looks like What is missing Where to look first
static and no parentheses The definition of a static member variable The class’s .cpp file, for a line with ClassName:: before the variable
virtual and a function The body of a virtual function The class’s .cpp file, for the function body
ClassName::GetMessageMap BEGIN_MESSAGE_MAP … END_MESSAGE_MAP The .cpp file at the start of the line, for that class’s message map
Neither static nor virtual Often the body of a message handler The ON_ lines in the message map of the .cpp file at the start of the line

A static member variable without a definition

public: static followed by a name with no parentheses is a static member variable. The type is spelled out as a long CStringT<...>, which is how CString expands. The variable name is at the end: CStockDlg::s_sLastFolder.

The static line inside the class only declares the variable. No storage exists for it until one .cpp file defines it with the class name in front:

// StockDlg.h (inside the class): declaration
static CString s_sLastFolder;

// StockDlg.cpp (outside any function): definition
CString CStockDlg::s_sLastFolder;

A virtual function without a body

CCsvExporter::WriteRow is marked virtual. The derived class declares the override in its header, but no .cpp file has the body.

Nothing in the program calls WriteRow on a CCsvExporter, and it is still unresolved. The object file at the start of the line is StockExporter.obj, which contains the constructor of CCsvExporter. The constructor sets up the object’s table of virtual functions, and that table holds the address of WriteRow. When a search for callers finds nothing, check whether the .cpp file at the start of the line constructs an object of that class.

If the override is needed, write the body in the class’s .cpp file. If it is not, remove the declaration from the header so the base class version is used.

// 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 is unresolved: BEGIN_MESSAGE_MAP is missing

If the line names a GetMessageMap that you never wrote, a message map is missing. In MFC’s afxwin.h, DECLARE_MESSAGE_MAP only declares two functions, GetThisMessageMap and GetMessageMap. Their bodies are written by BEGIN_MESSAGE_MAP in the .cpp file.

So a class with DECLARE_MESSAGE_MAP in the header and no BEGIN_MESSAGE_MAP in its .cpp file produces this error. It happens when a class header is copied from another project without its message map, or when the message map is deleted because the last handler was removed.

If the class keeps DECLARE_MESSAGE_MAP, give it a message map even when there are no handlers. The second argument is the base class. If the class does not need a message map at all, remove DECLARE_MESSAGE_MAP from the header instead.

// SettingsDlg.cpp
BEGIN_MESSAGE_MAP(CSettingsDlg, CDialogEx)
END_MESSAGE_MAP()

A plain member function: only the message map entry is left

OnBnClickedExport on the first line has neither static nor virtual, and it still has no caller in the error. No code calls it. The only place it appears is one entry in the message map:

BEGIN_MESSAGE_MAP(CStockDlg, CDialogEx)
	ON_BN_CLICKED(IDC_BTN_EXPORT, &CStockDlg::OnBnClickedExport)
	ON_BN_CLICKED(IDC_BTN_SETTINGS, &CStockDlg::OnBnClickedSettings)
END_MESSAGE_MAP()

In afxmsg_.h, ON_BN_CLICKED expands to ON_CONTROL, which puts the address of the function into one row of the message map table. Nothing is called there, so the linker has no calling function to report. This happens when the handler’s body is deleted from the .cpp file but its ON_ line stays.

Search the message map in StockDlg.cpp, the file at the start of the line, for the function name. If the button still needs to do something, write the handler. If it does not, delete both the ON_ line and the afx_msg declaration in the header.

When the name does not match these cases

Take the class-qualified part of the name, such as CStockDlg::s_sLastFolder, and search the whole solution for it. If you find only the declaration, the definition is missing. If you find a definition, check that its .cpp file is part of the build, and that the parameter types, const, and spelling match the declaration exactly. Any difference changes the decorated name.

The /VERBOSE linker option shows which object files and libraries the linker reads. DUMPBIN /SYMBOLS lists the names defined in an .obj or .lib, so you can compare them with the decorated name in the error. When the missing symbol is in a library rather than your own code, such as a .lib that is not linked or one built for the other architecture, the causes in the Compilation and link issues section of the LNK2001 page apply.

References