缘起
最近,在加班的过程中遇到一个链接错误 —— fatal error LNK1120: 1 unresolved externals
。这种错误是老朋友了,对我这种常年写 bug
的老手来说,完全不是事儿,轻松+愉快。
根据以下的排查思路基本上能解决大多数链接错误:
既然报了链接错误,说明编译已经通过了,问题基本出现在库文件上。
有可能是找不到库文件(缺少库,或者库文件搜索路径不对),可以先确认工程配置是否正确或者使用 /verbose:lib
查看链接过程。
也可能是库文件不对(没包含对应的导出符号),可以通过 dumpbin /exports error.lib > error.txt
查看 lib
库中的导出符号。按照以上步骤排查基本上可以解决绝大多数链接错误。
好的,让我们一起来实战一下吧。
说明: 实际项目需要保密,本文的截图是我在本地用测试工程做的。
确保 lib 存在并且路径正确
通过查看工程设置可以得知,依赖的库文件是 TestLNK1120Dll.lib
,附加库目录中也添加了这个 lib
所在的路径。肉眼看上去没问题。为了保险(之前遇到过更诡异的错误,配置看上去都对,但是实际的值不对),还是通过 /verbose:lib
选项看一下链接过程。
在对应工程上,右键 -> 属性 -> Configuration Property -> Linker -> Command Line
,在 Additional Options
下面输入 /verbose:lib
即可。
设置好后,重新编译。可以看到链接时整个库查找过程,如下图。
至此,可以确定库文件路径配置没错,那大概率是库文件中的符号与程序中的符号不匹配导致的。
说明: 如果找不到
.lib
文件,报错应该类似下面这样:
fatal error LNK1104: cannot open file 'TestLNK1120Dll.lib'
查看 lib 文件中的导出符号
使用 dumpbin
可以查看 lib
库中的所有导出符号,命令如下:
dumpbin /exports d:\test\TestLNK1120.lib > d:\TestLNK1120.txt
注意: 以上命令需要在
dumpbin.exe
所在目录下执行,或者启动Developer Command Prompt for VS xxx
。
然后根据 vs
编译错误提示中的符号在 TestLNK1120.txt
中搜索,应该是没有匹配项目。
搜索报错的符号
确实可以在 TestLNK1120.txt
中根据函数名 GetStaticData
搜到相关记录,但是根据完整的符号名称搜不到。
TestLNK1120.txt
文件中与 GetStaticData
相关的内容是:
?GetStaticData@@YAAAV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@XZ (class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > & __cdecl GetStaticData(void))
而 vs
报错提示是:
TestLNK1120.obj : error LNK2001: unresolved external symbol "class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > __cdecl GetStaticData(void)" (?GetStaticData@@YA?AV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@XZ)
让我们对比一下这两个结果哪里不一样。
对比
别看上面的输出很多很乱,其实我们最需要关心的是经过名字改编的符号名。
vs
中经过名字改编后的符号名是
?GetStaticData@@YA?AV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@XZ
lib
库中经过名字改编后的符号名是
?GetStaticData@@YAAAV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@XZ
如果仔细看,确实能发现这两个输出结果是不一样的,但是经过编译器处理的符号名非常不适合人类阅读。如果能看到函数原型就太好了。
其实,上面的输出结果既包含了函数原型,又包含了经过名字改编后的符号名。
vs
中的函数名:
class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > __cdecl GetStaticData(void)
lib
库中的函数名:
class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > & __cdecl GetStaticData(void)
可以发现,lib
库中提供的函数的返回值是引用类型的,而 vs
中函数的返回值是不带引用的。
至此就破案了!
undname 工具
如果只有经过名字改编后的符号名,可以通过微软提供的 undname.exe
工具来翻译成人类友好的名称。
示例工程
可以到 gitee 上下载示例工程。
总结
dumpbin.exe
可以查看 lib
库中的符号信息
undname.exe
可以非常方便的查看未经过编译器处理的函数名