ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

VS升级后LNK1104、LNK2019频发?附加依赖项排查与修复指南

VS升级后LNK1104、LNK2019频发?附加依赖项排查与修复指南 这事我碰到过三次了。每次都是某台开发机把 Visual Studio 升了级隔天就有人在群里贴出一大片红色链接错误。第一次我自己也被 LNK1104 和 LNK2019 折磨了整整一个下午翻了无数遍项目属性最后才反应过来源码没动过问题全出在“链接器 → 输入 → 附加依赖项”那一串 lib 名字和库路径上。Visual Studio 升级从来不只是换一个更亮的 IDE 皮肤。它带着新的 MSVC 工具集、新的 Windows SDK、新的默认库目录一起来了。你原来工程里手写的那些依赖信息可能还在指向旧版本的目录、旧名字的库升级后链接器按你给的地址找过去发现连文件都不存在。这篇文章专门聊升级后怎么改附加依赖项哪些配置会失效、为什么失效、具体怎么改以及如何用属性表把这些配置收拢到一处下次升级不用从头再踩一遍。内容面向 Windows 平台上做 C/C 开发的朋友尤其是老项目升级后链接阶段报错的场景。没有太多高深理论就是实打实的排查思路和可抄作业的步骤。1. 升级到底改了什么先定位问题的根源1.1 从 v141 到 v143链接器换了一套“导航地图”很多人在项目属性里见过“平台工具集”这个选项但平时不太会动它。Visual Studio 2017 对应的是 v1412019 是 v1422022 是 v143。这个数字不是随便写的它决定了整套 MSVC 编译器和链接器版本也决定了默认的 include 和 lib 搜索路径。升级之后Visual Studio 会弹一个“重新定向项目”的提示帮你把工具集自动改到新版本。它看起来什么都没变但背后的路径已经换了。举个例子旧环境里链接器默认去安装目录下的VC\Tools\MSVC\14.16.27023\lib\x64找标准库文件新环境变成了14.3x.xxxxx目录。如果你在“VC 目录 → 库目录”或者“链接器 → 常规 → 附加库目录”里填了基于旧版本号写死的路径升级后必然失效。Windows SDK 也是一样的逻辑。老项目多半用的是 8.1 或者某个旧的 10.0.xxxxx 版本 SDK升级后默认会改用新版 SDK库目录从10.0.16299.0\lib\um\x64变成10.0.26100.0\lib\um\x64。目录名里带了完整版本号但凡你手动引用过升级后这条路就断了。1.2 升级能自动改配置但改不了你手写的依赖重定向向导能自动处理的是像平台工具集、Windows SDK 版本这种“项目级别”的选项。它读取 vcxproj 里的属性节点匹配新环境里对应的值。但你自己在附加依赖项里填的opencv_world4100.lib、在附加库目录里写的D:\libs\opencv\build\x64\vc15\lib这些内容向导根本看不懂也不会帮你改。升级后你打开项目属性很可能看到附加依赖项里还躺着一堆旧世界的产物旧版本 SDK 里存在的库名新 SDK 里改名了。第三方库按 VS 版本分了子目录比如vc14、vc15、vc16你的路径还停在旧版本。某些库被拆成了多个比如老的foo.lib在新版本里拆分成了foo_core.lib和foo_utils.lib。项目之前用的静态库是你自己编译的升级后头文件没变但库内部依赖的 CRT 版本变了导致符号对不上。这一节想说明白一个核心观点升级后报链接错误几乎都不是你代码写错了而是“依赖描述”与“新环境”脱节。我们接下来的所有操作都是在把这份依赖描述重新校准到新环境上。2. 附加依赖项是个什么机制链接器的“前置弹药”2.1 附加依赖项的本质让链接器自动带上这些库先讲透一件事附加依赖项到底在干什么。C/C 工程的编译分两步。编译器把.cpp文件编成.obj每个 obj 里都有一些“待解答”的外部符号比如你调用了某个函数但没写实现编译器就会在 obj 里记一条“我需要一个叫 xxx 的符号”。链接器拿到这些 obj 之后需要去一堆.lib文件里找答案。问题来了如果没人告诉链接器去哪找它只会在默认库目录里翻翻不到就报 LNK2019。附加依赖项就是干这个的。你在“链接器 → 输入 → 附加依赖项”里每填一个.lib链接器就会把库名作为默认依赖带上等于是提前告诉它“你需要去这个仓库里找符号”。这里有个值得注意的细节附加依赖项可以写多个库用分号隔开或者一行一个都行它还能识别宏比如$(QtDir)\lib\Qt5Core.lib也可以写相对路径。我见过不少人在里面写绝对路径比如D:\libs\openssl\lib\libssl.lib这种做法短期能用但机器一换、目录结构一动必然出问题。后面我会说怎么用宏和相对路径替代硬编码。2.2 别忽视配置和平台四个配置是四套完全独立的设置这是升级后最容易翻车的地方我必须单独拎出来说。一个标准 C 工程的属性页顶部有两个下拉框“配置”和“平台”。组合起来就是 Debug x64、Release x64、Debug Win32、Release Win32 四套设置。附加依赖项这四套是各自独立的你在“Release x64”里配好的库不会自动出现在“Debug x64”里。升级后常见的翻车现场是这样的编译时活动配置是 Release x64你发现依赖项和库目录不对改完编译通过松了口气。然后你切到 Debug 一编译报错又来一遍你大惊失色“我刚才不是改了吗”是的改了但只改了 Release x64。所以修改附加依赖项的第一步是把配置和平台都检查一遍。如果你只关心 x64那就把 Win32 的依赖清掉或者忽略如果 Debug 和 Release 用的库本来就不一样更要逐套核对。升级前最好先用“配置管理器”看一眼你工程里激活了哪些配置组合别对着一个空配置折腾半天。2.3 属性页配置与 #pragma comment(lib) 的关系除了属性页里手工填C 还有一种常见的写法就是直接在源码里写#pragma comment(lib, foo.lib)。这句指令会让编译器把它写进 obj 文件链接时同样自动带上这个库效果上等价于在附加依赖项里填foo.lib。两者各有利弊。属性页的好处是依赖信息集中后续维护的人打开项目属性就能看到全貌缺点是升级后每一个配置都得改容易漏。#pragma comment(lib)的好处是库和代码写在一起你拷贝源码文件时依赖跟着走缺点是依赖分散在代码里版本升级时你得全局搜索然后一个个改。我个人的习惯是第三方预编译库依赖走属性页自己项目内部生成的库可以用#pragma comment(lib)但前提是每个用到它的源文件都写一遍少一个就出一个 LNK2019非常隐蔽。升级后如果发现 LNK2019而属性页里的附加依赖项看起来没问题不妨全局搜一下#pragma comment看看是不是源码里写死了旧库名。3. 升级后修改附加依赖项的完整实操3.1 动手前先备份和记录旧依赖升级这件事听起来简单但工程里的依赖配置可能分布在四套配置、多级属性表里直接改很容易改乱。我的习惯是动手前先做一次“现状快照”。打开项目属性逐个记录当前使用的工具集版本、Windows SDK 版本、附加依赖项内容、附加库目录、运行库选项。不用手抄项目文件本身就是 XML直接用文本编辑器打开.vcxproj搜索AdditionalDependencies和AdditionalLibraryDirectories能看到所有写死的库和路径。把关键段落复制到一个临时文件里存着万一改坏了还能对照着恢复。这里多说一句.vcxproj里的内容平时最好不要手改但只读检查没问题。如果你发现某个附加依赖项下面凑着一长串内容是来自$(SolutionDir)之类宏展开的值也别慌在 Visual Studio 里看属性页底部那一栏“继承的值”那里显示的是从属性表或上级目录带下来的完整清单。3.2 逐项核对附加依赖项和库目录实操步骤我按顺序写一遍跟着做就行。打开项目右键项目名称选“属性”。确认顶部“配置”和“平台”是正确的组合可以先从“所有配置”和“所有平台”开始改能省不少事。展开“链接器 → 输入”查看“附加依赖项”。把每个.lib名挨个检查这个库现在还存在吗名字有没有变如果在附加库目录里搜不到它大概率是名字或版本路径变了。展开“链接器 → 常规”查看“附加库目录”。重点看有没有写死版本号的路径比如vc15、10.0.16299.0这种把它们更新到新环境对应的目录。展开“VC 目录”看“库目录”。这里通常包含系统默认的库路径比如$(VC_LibraryPath_x64)、$(WindowsSDK_LibraryPath_x64)一般不需要手动改。但如果你发现某一行是旧版本工具的绝对路径就要删掉或者换成新版。全部改完后记录当前配置和平台组合切换到其他配置重复同样的检查。第三步特别容易漏。很多人只盯着“附加依赖项”看却忘了库目录已经失效了。比如你填了opencv_world4100.lib文件名没变但附加库目录还指着一个根本不存在的文件夹链接器照样报 LNK1104。库文件名和库目录是两件事必须同时核查。3.3 用属性表统一管理依赖一劳永逸的办法如果说上面这些步骤是“治标”那么用属性表就是“治本”。Visual Studio 有个功能叫“属性管理器”默认躲在“视图 → 其他窗口”里。打开后你会看到项目下面有几个节点分别对应不同配置每个节点下面可能有继承的 Props 文件。右键选择“添加新项目属性表”创建一个Deps.props之类的文件把附加依赖项、附加库目录、甚至预处理宏都放进这个属性表。属性表的好处是它可以被多个项目共享。假设你一个解决方案里有十几个工程全都用 OpenCV 和 protobuf以前你得在十几个工程的属性页里各改一遍。用属性表只需要在一个公共的 Props 文件里改所有引用的工程同时生效。升级 Visual Studio 之后也一样库目录变动只改一个文件就全方案搞定。属性表的优先级需要理解一下项目属性页里手动设置的值会覆盖属性表里的同名设置但属性表里通过%(AdditionalDependencies)继承下来的值会合并。所以你在属性表里写opencv_world4100.lib;%(AdditionalDependencies)工程属性里再单独加一个库两个会合并传给链接器。这个行为很重要能避免“覆盖”和“丢失”的问题。4. 升级后最常撞上的链接错误与排查方法4.1 LNK1104链接器根本找不到库文件这是升级后最典型、也最好排查的报错。LNK1104: cannot open file foo.lib翻译过来就是链接器拿着你给的库名和搜索路径转了一圈没找到文件。原因就几类库名写错了或者改名了旧 SDK 里的库在新 SDK 里换名字这种情况在 Windows SDK 升级后很常见。路径失效附加库目录写死了旧版本号目录不存在。32/64 位不匹配工程是 x64库路径却指到 32 位库目录。这种报错经常伴随fatal error LNK1112: module machine type x86 conflicts with target machine type x64看到这个就要先查平台对不对。库是动态链接的.dll但工程只引用了.lib而.lib实际在另一个路径。排查思路先在附加库目录里手动找一遍找不到就用文件资源管理器全局搜一下这个库文件在哪。如果确实不存在去查这个库是不是换了版本目录或者干脆换成了别的库名。如果库存在但报错打开项目属性 → 链接器 → 命令行点“查看”按钮确认传给link.exe的/LIBPATH参数是否包含你设置的路径。4.2 LNK2019/LNK2001符号存在但没有连进来LNK2019: unresolved external symbol xxx referenced in function yyy和LNK2001是升级后更让人头疼的一类错误。报错信息等同于告诉你obj 文件里需要某个符号但链接器在所有你给定的库里都没找到。这里有个细节很多人不太注意链接器在处理静态库时是“按需拉取”的。它会遍历你给的库找到一个能匹配未解析符号的 obj 就拉进来。如果两个库之间存在依赖关系比如A.lib里的函数调用了B.lib里的函数那么命令行顺序上B.lib必须排在A.lib后面。如果你在附加依赖项里写的是A.lib在前可能要手动调整顺序。升级后出现 LNK2019还有一种隐蔽情况你依赖的库是用旧版本编译器编译的它内部链接的是旧版 C 运行库符号修饰规则可能已经有细微差异。最典型的就是_MSC_VER版本相关的代码分支导致同一个函数在旧库里的修饰名和新工程的查找名对不上。这类问题通常需要重新编译第三方库而不是在附加依赖项里反复加库名。排查 LNK2019 时我建议先看修饰名里的线索。如果符号是__imp_开头说明是导入库的问题如果带一长串std::类型修饰基本都是不同模块用了不同 C 标准库配置导致的。前者去检查导入库路径后者去检查运行库选项。4.3 运行库冲突LNK2038、LNK4098、LNK2005这三兄弟是“配置不齐”的典型表现升级后非常容易冒出来。LNK2038的报错文本一般是mismatch detected for RuntimeLibrary: value MDd_DynamicDebug doesnt match value MT_StaticRelease。通俗讲就是工程的某个 cpp 文件用的是/MDd动态调试运行库另一个模块用的是/MT静态发布运行库两边都往最终程序里塞 CRT符号冲突了。LNK4098是默认库和其他库的 CRT 引用冲突比如你显式链接了LIBCMT.lib但其他模块用的又是/MD两个 CRT 都想要初始化链接器直接崩。早期 MFC 或者老旧的第三方库经常有这个毛病。LNK2005出现通常是同一个符号被两个库各定义了一次可能是重复添加了同一个库也可能是静态库和 CRT 里的符号重名。这类问题的修复思路都在“项目属性 → C/C → 代码生成 → 运行库”这个选项里。一个解决方案里的所有项目尽量统一用同一种运行库Debug 用/MDdRelease 用/MD别一个开静态一个开动态。升级 Visual Studio 之后默认值通常会变成新工具集对应版本但你会发现自己写的老库还是旧工具集编出来的两边不一致就炸了。这时候优先选择重新编译那些旧库而不是硬调运行库选项去迁就它们。5. 升级之后防患于未然工程依赖维护的小习惯5.1 用内置宏写路径别把版本号写死很多人写附加库目录时习惯直接贴绝对路径这能在自己电脑上活很久但一到升级或者换机器就凉。Visual Studio 提供了一批内置宏专门用来指向当前环境的路径。常用的有这几个宏含义$(ProjectDir)当前项目文件所在目录$(SolutionDir)当前解决方案目录$(Platform)当前平台名x64 或 Win32$(Configuration)当前配置名Debug 或 Release$(VC_LibraryPath_x64)当前 MSVC 工具集的 x64 库目录$(WindowsSDK_LibraryPath_x64)当前 Windows SDK 的 x64 库目录组合使用效果很好。比如你把所有第三方库统一放在解决方案下的libs目录结构是libs/opencv、libs/protobuf那附加库目录可以写成$(SolutionDir)libs\opencv\lib;$(SolutionDir)libs\protobuf\lib。升级后只要解决方案目录结构不动这些路径永久有效。同理附加依赖项里的库名如果不是版本相关就不要带版本号比如opencv_world.lib就比opencv_world4100.lib抗升级。5.2 升级后的收尾检查清单改完附加依赖项只是第一步一个工程能在新环境里稳定编译还要检查其他几个高频雷区。我整理了一张检查清单升级后照着过一遍基本不踩坑。检查项在哪里看升级后要确认什么平台工具集项目属性 → 常规已经指向当前 VS 对应的工具集版本Windows SDK 版本项目属性 → 常规已经指向当前安装的 SDK 版本附加依赖项链接器 → 输入库名存在且和平台匹配附加库目录链接器 → 常规路径有效且不包含旧版本号运行库选项C/C → 代码生成全方案统一字符集常规 → 字符集升级后默认可能变化注意是否影响了__imp_符号预处理宏C/C → 预处理器某些库要求根据平台定义不同的宏语言标准常规 → C语言标准升级后默认值通常是最高版本但老代码可能按旧标准写这几点里字符集是很多人忽略的一项。升级后项目如果从“多字节字符集”悄悄变成了“Unicode 字符集”你依赖的某个库也许还是按 ANSI 导出符号链接阶段就报表面看不出来的 LNK2019。查一下属性页里“字符集”选项是不是还和升级前一致能省不少排查时间。5.3 一个额外的排查工具VS 自带的 dumpbin最后分享一个排查链接问题的利器dumpbin.exe。打开“开发者命令提示符”可以直接用它查看一个库导出了哪些符号。想知道某个.lib文件里有没有你需要的符号运行dumpbin /linkermember:1 foo.lib想知道某个.dll依赖了哪些其他库运行dumpbin /dependents bar.dll升级后如果拿不准一个库是否兼容新环境先用 dumpbin 看一眼它的头信息里的机器类型和依赖项比自己盲猜路径靠谱得多。这个工具在新旧 Visual Studio 里都有位置一般在VC\Tools\MSVC\版本号\bin\Hostx64\x64\dumpbin.exe。最后再多说两句我自己在升级这件事上吃过几次亏现在养成了一个习惯每次 Visual Studio 升级前把工程的vcxproj里AdditionalDependencies相关段落截图存档再把属性表的 props 文件单独复制一份放外面。升级中一旦出现链接错误第一件事不是去翻代码而是先对照旧清单看附加依赖项和库目录有哪些跟新环境对不上。说实话附加依赖项这种配置平时存在感很低不报错的时候你可能几个月都不会打开属性页看一眼但只要升级一次 Visual Studio它立刻变成优先级最高的工程。希望这篇内容能帮你少走一趟我当年走过的弯路。下次再遇到 LNK1104 或者 LNK2019记住一句话先去链接器输入那里看看你的弹药库地址是不是还停在旧世界。
返回列表