ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

VS2019中PCL配置:附加依赖项与LNK2019链接错误详解

VS2019中PCL配置:附加依赖项与LNK2019链接错误详解 1. 头文件能找到链接却失败附加依赖项在VS2019里到底管什么1.1 记录一段真实的LNK2019现场上周帮同事排查PCL 1.12.0的编译问题项目里#include pcl/io/pcd_io.h和#include pcl/point_types.h都能正常通过编译编辑器里的智能提示也正常跳转但一按F5就“翻车”输出窗口里刷出几十行LNK2019 无法解析的外部符号 bool __cdecl pcl::io::loadPCDFilestruct pcl::PointXYZ(...)他问我的第一句话是“头文件都进来了为什么还找不到函数”我打开项目属性看了一眼发现他只配置了“C/C - 常规 - 附加包含目录”把PCL的include目录填得明明白白但“链接器 - 输入 - 附加依赖项”那一栏完全是空的。这就是典型的只配了头文件没配.lib。头文件让编译器认识函数声明而.lib让链接器找到函数实现。在VS2019里你需要在“附加依赖项”里明确告诉链接器请把pcl_common.lib、pcl_io.lib这些库文件链接进来。这一栏漏掉哪怕include路径全对编译也会死在链接阶段。1.2 编译器、链接器和运行时的三方分工要理解附加依赖项为什么这么重要先得把VS2019的构建过程拆开看。一个C程序从源码到可以运行要经过预处理、编译、汇编、链接四个阶段其中和PCL配置关系最大的是中间的“编译”和后面的“链接”。编译阶段编译器处理.cpp文件遇到#include时会把头文件内容展开这时候它只需要知道函数长什么样也就是函数签名。比如pcl::io::loadPCDFile接收什么参数、返回什么类型。这个阶段如果报错说明头文件路径没配好或者头文件本身有语法兼容问题。链接阶段链接器要把各个.obj文件和外部库组合成可执行文件。链接器看到一个调用pcl::io::loadPCDFile的代码就需要去某个库文件里找这个函数的二进制实现。这个“某个库文件”就是.lib。如果你没告诉链接器具体链接哪些.lib或者链接器去错误目录找就会产生LNK2019、LNK1104这类错误。程序运行阶段可执行文件被加载后会动态加载.dll文件。PCL的架构是静态链接时链接.lib导入库运行时导入库会引导加载同名的.dll。比如链接了pcl_io.lib运行时程序会去找pcl_io.dll。所以.lib解决的是“链接期能不能找到符号”.dll解决的是“运行期能不能找到实现”两者差了整整一个阶段。很多人把“附加包含目录”“附加库目录”“附加依赖项”混为一谈其实三个是完全不同的东西配置项作用对应阶段附加包含目录告诉编译器去哪找头文件编译期附加库目录告诉链接器去哪找.lib文件链接期附加依赖项告诉链接器具体链接哪些.lib文件名链接期如果你把包含目录比作书面地址那附加库目录就是楼栋分布图附加依赖项则是你点名的访客名单。三者配齐了链接器才知道去哪栋楼找哪些人。2. 三种写入.lib依赖的方法按实际场景选型2.1 属性页手动添加最直观适合单项目快速配置这是绝大多数教程采用的方式也是新手上手最快的一种。具体路径是项目 - 属性 - 链接器 - 输入 - 附加依赖项操作步骤很简单点开“附加依赖项”右侧的下拉箭头选择“编辑”在弹出的对话框里直接把.lib文件名一行一个粘贴进去确定保存。但要注意这里编辑框里默认会有%(AdditionalDependencies)这个宏它的作用是从父级或项目默认设置继承系统库。我见过不少人在编辑时把这个宏删掉结果编译时连kernel32.lib都找不到了报出无数个Windows API相关的链接错误。所以正确姿势是把自己的.lib文件名写在前面保留最后的%(AdditionalDependencies)例如pcl_common.lib pcl_io.lib %(AdditionalDependencies)如果你需要在Debug配置下编译那么改成pcl_common_debug.lib pcl_io_debug.lib %(AdditionalDependencies)属性页手动添加的优点是完全可视化、改动即时生效缺点是配置只存在于当前项目文件.vcxproj里。换一台电脑、换一个项目就得重新填一遍。对于只跑通一两个Demo的入门阶段这个缺点可以接受但一旦项目多起来效率问题就暴露了。2.2 属性表方案一次配置整个团队复用VS2019里有一个不太显眼但极其好用的窗口叫“属性管理器”在“视图”菜单下。打开属性管理器后会看到当前项目下面按“Debug | Win32”“Release | Win32”“Debug | x64”“Release | x64”分好了节点。右键任意节点选择“添加新项目属性表”输入名称后就会生成一个.props文件。.props文件本质上是一个XML配置里面可以保存包含目录、库目录、附加依赖项、预处理器定义等所有项目属性。你可以在一个节点下写完PCL配置然后把这个.props文件单独拷出来。以后新建项目只要右键“添加现有属性表”选中这个文件所有配置就自动加载了。我实际的建议是创建两个属性表一个叫PCL_Debug.props一个叫PCL_Release.props。Debug的附加依赖项里写pcl_common_debug.lib这一套Release里写pcl_common.lib这一套。然后把两个属性表分别附加到对应配置节点上这样Debug和Release切换时链接的库文件不会串。如果不想维护两个文件也可以只创建一个.props里面用Condition区分配置。参考内容如下ItemDefinitionGroup Link AdditionalDependencies Condition$(Configuration)Debug pcl_common_debug.lib; pcl_io_debug.lib; %(AdditionalDependencies) /AdditionalDependencies AdditionalDependencies Condition$(Configuration)Release pcl_common.lib; pcl_io.lib; %(AdditionalDependencies) /AdditionalDependencies /Link /ItemDefinitionGroup属性表方案看起来比手动添加复杂但真正进入多项目阶段后它是节省时间最明显的方式。我在处理PCL VTK Boost这种依赖特别多的场景时几乎全靠.props文件。重装系统或者换电脑把.props往新项目里一挂五分钟恢复全部配置。2.3 #pragma comment库代码级方案适合单文件验证第三种方式是在源码顶部直接写#pragma comment(lib, pcl_common.lib) #pragma comment(lib, pcl_io.lib)这个预处理指令的语义是告诉链接器把指定的库文件当作附加依赖项链接进来。它和属性页里的“附加依赖项”最终效果一致但作用域只限于该编译单元所在的模块。适用场景主要有两个一是快速验证某个函数是否在该库中不想为了一个小测试去改项目属性二是写开源示例或分享Demo时希望源码自带依赖关系别人拿到代码编译就能过。但这个方案有个很容易踩的坑#pragma comment(lib, pcl_common.lib)写死在源码里不区分Debug和Release。你在Debug模式下编译它照样链接Release版的pcl_common.lib。好在PCL的Debug库文件名带了_debug后缀可以用宏自动选择#ifdef _DEBUG #pragma comment(lib, pcl_common_debug.lib) #pragma comment(lib, pcl_io_debug.lib) #else #pragma comment(lib, pcl_common.lib) #pragma comment(lib, pcl_io.lib) #endif这种方式最大的缺点是侵入性太强不属于这个模块的代码也会参与链接。如果项目里同时有好几个大型库都用#pragma comment(lib)到头来还是得靠属性页或属性表统一管理。三种方式可以灵活混用日常开发用属性表临时验证用#pragma comment(lib)给别人的示例代码里用源码方式最省心。3. PCL 1.12.0配置实录从安装包到跑通点云示例3.1 安装包选择与安装后目录确认PCL 1.12.0在Windows下通常使用官方预编译的AllInOne安装包名称类似PCL-1.12.0-AllInOne-msvc2019-win64.exe。注意这个命名里的两个关键信息msvc2019对应VS2019的v142工具集win64对应64位平台。这也是为什么很多人在VS2017或VS2022里配置PCL 1.12.0时会遇到各种奇怪的兼容问题。工具集版本不匹配编译器版本和预编译库所使用的C运行时库不匹配链接阶段就会报一堆与std::相关的LNK2019或者运行时报DLL初始化失败。所以我一直建议用哪个VS版本就选对应版本的PCL安装包。PCL 1.12.0就是配VS2019用的不要拿它硬往VS2015或VS2022上凑。安装完成后进入安装目录正常情况下应该能看到这样的结构D:\PCL 1.12.0 ├── 3rdParty │ ├── Boost │ ├── Eigen │ ├── FLANN │ ├── OpenNI2 │ ├── Qhull │ └── VTK ├── bin ├── cmake ├── include └── lib安装目录里最好去掉空格比如我习惯装到D:\PCL112。虽然VS2019处理带空格的路径已经没什么大问题但后续如果接CMake或者其他工具链路径带空格始终是个隐患。安装包通常会帮你设置PCL_ROOT环境变量指向安装根目录。这一步值得确认一下右键“此电脑” - 属性 - 高级系统设置 - 环境变量看看系统变量里有没有PCL_ROOT。有的话后面的路径都可以用$(PCL_ROOT)前缀代替能省不少事。还要确认PATH里是否已经加入了PCL和第三方库的bin目录。这个关系到运行时能不能找到DLL。PCL 1.12.0需要加入PATH的目录至少有$(PCL_ROOT)\bin $(PCL_ROOT)\3rdParty\Boost\lib $(PCL_ROOT)\3rdParty\FLANN\bin $(PCL_ROOT)\3rdParty\OpenNI2\Tools $(PCL_ROOT)\3rdParty\Qhull\bin $(PCL_ROOT)\3rdParty\VTK\bin有些版本的安装包不会自动把这些路径全部写进PATH所以还是手动确认一遍更稳妥。改完环境变量后已打开的VS2019需要重启才能读到新的环境变量。3.2 逐项核对包含目录、库目录、附加依赖项一个都不能少打开项目属性后第一件事不是急着填路径而是先在“配置管理器”里确认当前用的是哪个“配置”和“平台”。PCL 1.12.0预编译库是x64的所以平台必须选x64如果项目当前处于Win32平台后面全白配。配置下拉框里选Debug或Release先选一个我习惯从Debug开始。在“C/C - 常规 - 附加包含目录”里需要加入的是PCL及第三方库的头文件路径。以PCL_ROOTD:\PCL 1.12.0为例$(PCL_ROOT)\include\pcl-1.12 $(PCL_ROOT)\3rdParty\Boost\include $(PCL_ROOT)\3rdParty\Eigen\eigen3 $(PCL_ROOT)\3rdParty\FLANN\include $(PCL_ROOT)\3rdParty\Qhull\include $(PCL_ROOT)\3rdParty\VTK\include\vtk-9.1这里有个细节$(PCL_ROOT)\include\pcl-1.12注意最后多了一层pcl-1.12目录。安装包include目录下并不是直接放头文件而是先按版本号建了一层文件夹头文件实际在include\pcl-1.12\pcl\...路径下。这个版本号子目录是PCL 1.10之后的标准做法很多初学者在这里漏掉一层导致找不到头文件。在“链接器 - 常规 - 附加库目录”里加入$(PCL_ROOT)\lib $(PCL_ROOT)\3rdParty\Boost\lib $(PCL_ROOT)\3rdParty\FLANN\lib $(PCL_ROOT)\3rdParty\Qhull\lib $(PCL_ROOT)\3rdParty\VTK\lib如果用到OpenNI2深度传感器再额外追加$(PCL_ROOT)\3rdParty\OpenNI2\Lib作为库目录以及对应的include目录和bin目录。最后才是本文的主角“链接器 - 输入 - 附加依赖项”。这里填的是具体的.lib文件名不是路径。链接器会拿着这些文件名去上面配置的库目录里逐个搜索。3.3 PCL附加依赖项清单从最小集开始不要一次全塞PCL的lib目录下会有几十个库文件主要负责点云滤波的有pcl_filters.lib负责配准的有pcl_registration.lib负责分割的有pcl_segmentation.lib负责可视化的有pcl_visualization.lib。如果你一次把全部库都塞进附加依赖项会大大增加链接时间而且有些库之间还有版本兼容问题报出来的错误会让你无从下手。我推荐的做法是先配一个“最小依赖集”缺什么再加什么。如果只是读取并处理点云通常需要pcl_common.lib pcl_io.lib pcl_kdtree.lib pcl_search.lib pcl_octree.lib pcl_filters.lib pcl_sample_consensus.lib pcl_features.lib pcl_segmentation.libDebug下对照改成_debug后缀版本pcl_common_debug.lib pcl_io_debug.lib pcl_kdtree_debug.lib pcl_search_debug.lib pcl_octree_debug.lib pcl_filters_debug.lib pcl_sample_consensus_debug.lib pcl_features_debug.lib pcl_segmentation_debug.lib如果要写可视化代码、创建窗口弹出来显示点云单靠PCL库还不够。因为PCL的可视化模块底层依赖VTK所以要在附加依赖项里追加VTK的.lib文件。VTK 9.1的导入库文件名带版本后缀格式类似vtkCommonCore-9.1.lib vtkCommonDataModel-9.1.lib vtkCommonMath-9.1.lib vtkCommonMisc-9.1.lib vtkCommonSystem-9.1.lib vtkCommonTransforms-9.1.lib vtkInteractionStyle-9.1.lib vtkRenderingCore-9.1.lib vtkRenderingFreeType-9.1.lib vtkRenderingOpenGL2-9.1.lib vtkRenderingUI-9.1.lib具体版本号以你安装包里的VTK目录为准打开3rdParty\VTK\lib看一眼就知道。VTK的库文件名很长手动敲容易拼错建议在资源管理器里复制文件名再粘贴到附加依赖项里。记住一个原则只加用得到的模块。用loadPCDFile只需要pcl_io.lib别把pcl_visualization.lib和一堆VTK库全都塞进来。等到真正要显示点云时再加可视化相关库。3.4 用一个最小工程验证配置是否生效配置完成后可以创建一个简单的控制台程序验证。用下面的代码测试读点云和写点云#include iostream #include pcl/io/pcd_io.h #include pcl/point_types.h int main() { pcl::PointCloudpcl::PointXYZ cloud; cloud.width 32; cloud.height 1; cloud.is_dense false; cloud.points.resize(cloud.width * cloud.height); for (std::size_t i 0; i cloud.points.size(); i) { cloud.points[i].x static_castfloat(i); cloud.points[i].y static_castfloat(i % 8); cloud.points[i].z 1.0f; } pcl::io::savePCDFileASCII(test_cloud.pcd, cloud); pcl::PointCloudpcl::PointXYZ::Ptr loaded(new pcl::PointCloudpcl::PointXYZ); if (pcl::io::loadPCDFilepcl::PointXYZ(test_cloud.pcd, *loaded) -1) { std::cerr Load PCD file failed. std::endl; return -1; } std::cout Loaded points: loaded-size() std::endl; return 0; }这段代码会生成一个32个点的PCD文件再读回来并打印点数。如果编译链接全部通过说明PCL的基础库配置已经正确。接下来再跑数据处理和可视化就可以按需增补模块了。有两点要特别说明。第一这里保存的点云文件会生成在“当前工作目录”不一定在源代码同目录运行的时候留意一下路径。第二如果Debug配置下编译不过先检查附加依赖项里所有文件名是否都带_debug同理Release配置下不能出现_debug后缀的库。PCL对Debug和Release库的区分非常严格混用会产生诡异的运行期崩溃。4. 配置完成后的链接错误与运行错误排查4.1 LNK1104无法打开.lib文件先从路径和文件名排查最常见的链接错误是LNK1104: 无法打开文件 pcl_common.lib这个错误的字面意思很明确链接器知道你要链接pcl_common.lib但在所有“附加库目录”里都找不到这个文件。按下面的顺序排查基本能覆盖九成的情况附加库目录有没有写对用$(PCL_ROOT)前缀时确认环境变量PCL_ROOT真的存在且指向正确目录。文件名有没有拼错检查是pcl_common.lib还是pcl_common_debug.lib少个_debug后缀在Debug配置下就是找不到文件。目录里到底有没有这个文件直接打开资源管理器确认lib目录下文件名和附加依赖项里完全一致。平台的位数对不对PCL 1.12.0预编译库是x64如果你在Win32平台下链接x64库目录也会报LNK1104或兼容性错误。路径里有没有莫名其妙的引号VS的附加库目录不需要手写引号带空格的路径也能正常处理。4.2 LNK2019无法解析的外部符号通常不是少一个.lib而是缺一类LNK2019的报错格式是LNK2019: 无法解析的外部符号 public: int __cdecl pcl::PCLPointCloud2::..., 该符号在函数 ... 中被引用很多人一看到LNK2019就以为是漏了一个库文件于是去网上复制一大串lib列表全塞进附加依赖项。其实这个错误的本质是你调用的某个符号在已链接的库集合里找不到。可能的原因有三个。一是确实遗漏了某个模块的库。比如你调用了pcl::visualization::PCLVisualizer却只链了pcl_common.lib和pcl_io.lib那必然报vtk相关符号找不到。这时看报错里出现的类名或函数名反推它属于哪个模块再去补对应的库。比如报错里出现pcl::visualization就去加上pcl_visualization.lib和VTK的库。二是依赖链断裂。PCL的库文件之间不是孤立的pcl_io.lib内部依赖pcl_common.libpcl_visualization.lib依赖pcl_io.lib和VTK库。链接器按顺序处理库如果列表里前面的库依赖了后面的库顺序错了也可能报LNK2019。微软的链接器一般会遍历查找但某些场景下顺序依然敏感。我的建议是把PCL的库按字母顺序排VTK的直接放在PCL之前或之后都行实测下来最不容易出问题。三是编译器和库版本不匹配。这是最隐蔽的。PCL 1.12.0的预编译库是用VS2019的v142工具集编的如果你用VS2022的v143工具集打开项目C标准库的符号修饰可能不一致导致一堆std::相关的LNK2019。这种情况下补多少个.lib都没有用要么换VS2019要么自己用当前工具集源码编译PCL。4.3 编译链接都通过了运行时报缺DLL的坑链接全部通过程序也能生成exe但一运行就弹“由于找不到pcl_common.dll无法继续执行代码”这个问题在PCL新手教程里出现频率极高。原因不难理解链接期通过.lib找到了函数引用但运行期程序会去加载同名的DLL。Windows加载DLL时会按“应用程序目录 - 系统目录 - PATH环境变量目录”的顺序查找。如果PCL的bin目录没加入PATH或者加入PATH后VS没有重启系统就找不到DLL。把$(PCL_ROOT)\bin和3rdParty\VTK\bin等目录加入系统PATH后记得重启VS2019否则环境变量不会生效。还有一种比较隐蔽的情况系统里存在多个版本的PCLPATH里较早的目录指向了旧版本程序加载了不匹配的DLL也会运行时报错而且报错信息可能不那么直观。有个小技巧可以快速定位运行exe后打开“任务管理器 - 详细信息”右键相关进程选择“打开文件所在位置”看看实际加载的是哪个目录下的DLL。也可以在工作目录下用dumpbin /dependents查看exe的导入表确认它期望的DLL列表。4.4 Debug和Release混用编译期不报错但运行期崩溃这一类问题最让人头疼。Debug配置下链接了Release库或者反过来编译期通常不会报错但运行到某个逻辑时会莫名崩溃堆栈信息完全看不出和PCL有什么关系。PCL 1.12.0对Debug和Release库的区分非常明显文件名后缀就是_debug。但如果用了#pragma comment(lib)方案宏判断写错就容易在Debug配置下链接到Release版库。运行时因为C运行库版本不一致排查起来很耗时间。最稳妥的做法是Debug配置下附加依赖项里只出现*_debug.lib文件Release配置下只出现不带后缀的.lib文件。如果项目属性设置正确链接器又有“忽略特定默认库”的配置建议不要轻易动这个选项否则系统库也会被一并忽略。5. 把PCL配置固化下来一劳永逸的几个习惯前面几章解决的是“怎么配”和“出错了怎么排查”的问题这一章聊点我在实际项目中反复验证过的操作习惯。先说属性表。如果你手头有多个项目都要用PCL不要再每个项目手动配一次了。在属性管理器里创建好PCL属性表后可以独立保存成.props文件放到一个公共目录里比如D:\LibConfigs\PCL_Debug.props和D:\LibConfigs\PCL_Release.props。新项目打开属性管理器右键节点添加这个现有属性表配置就全部进来了。团队协作时把这些.props文件提交到Git仓库每个成员拉下来都能在第一时间获得一致的配置不用再对着聊天记录里的零散路径人工复刻。再说路径写法。所有路径尽量用$(PCL_ROOT)环境变量开头不要用硬编码的D:\PCL 1.12.0。原因很简单不同人的安装位置可能不一样重装系统后盘符也可能变化。只要环境变量存在VS里用$(PCL_ROOT)就能自动解析。如果某个路径前缀让你纠结那就先打开CMD跑一句echo %PCL_ROOT%确认变量的值。还有一个很容易忽视的点PCL 1.12.0的安装包里除了lib还有大量DLL。很多人配置完属性表链接器输入都填好了运行却提示缺DLL。我的做法是把bin目录和3rdParty\VTK\bin等关键目录都放进系统PATH而不仅仅是项目属性。这样在开发调试阶段不用频繁拷贝DLL到exe目录。另外在每次改完PCL配置后“重新生成解决方案”一次不要只按F5。F5可能只触发增量编译而PCL相关的头文件和链接选项改动有时不会触发所有依赖项的重新编译。遇到“明明改了配置还是不生效”的情况执行“生成 - 重新生成解决方案”大概率能解决。最后一个建议保留一份“能跑通的最小配置”清单。配置库最怕的是一步步加东西加到最后不知道哪一步是关键。我一般会写一个README里面记清楚“附加包含目录”“附加库目录”“附加依赖项”三栏分别填了什么以及Debug/Release各用什么后缀。这份清单跟着项目走比任何网上的教程都更贴合你自己的环境。PCL 1.12.0不是第一个让我在VS2019里折腾半天的库也不会是最后一个。回头看附加依赖项这个配置项本身并不复杂复杂的是一旦漏了它编译器不会给你任何提示链接器给的报错又不那么直观。把原理想清楚一套自己的配置模板这类问题基本就不会再回头找你了。
返回列表