马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
在 Visual Studio 里做 C++ 开发,CMake 已经是事实上的构建标准。当项目需要用到 OpenCV 这类第三方库时,很多人卡在“头文件找不到”“链接报 unresolved external symbol”。这篇以 OpenCV 为例,讲清楚 CMake 中链接第三方库的完整套路。
一、为什么要链接第三方库
- 提升效率:第三方库提供了已经实现并经过测试的功能,直接调用即可,不必重复造轮子。
- 扩展功能:图形界面库、数据处理库、网络库等,用它们可以轻松给程序加新能力。
- 提升质量:成熟第三方库经过广泛测试和使用,质量与可靠性更有保障,能减少错误和漏洞出现的概率。
- 节省成本:不必为某些功能投入大量时间和人力,自行开发的代价往往更高。
- 社区支持:活跃社区能帮你更快解决问题,也能从他人的贡献里学到最佳实践。
二、CMake 项目的基本结构
安装好 CMake 之后,用 Visual Studio 创建跨平台 CMake 项目。完成后一般是一个总项目加子项目的结构:
- 总项目负责整体配置、公共编译选项,并用
add_subdirectory 把子项目引进来。
- 子项目里用
add_executable 把源码和可执行目标绑定,再用 include 与 link 两步把第三方库接进来。
三、总项目的 CMakeLists
# 设置 CMake 最小版本
cmake_minimum_required(VERSION 3.8)
# 设置项目名字
project("Main")
# 配置编译器参数
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zi")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /openmp")
# 包含子项目
add_subdirectory("TestCMake")
编译器参数可以按需要开关:想开启 OpenMP 支持,用 set 把对应选项加进 CMAKE_CXX_FLAGS 即可。/Zi 生成调试信息、/MP 开启并行编译,这两个对调试体验和编译速度都很有帮助。
四、子项目的 CMakeLists:三步接第三方库
子项目里最关键的就是两步:include_directories 把头文件目录加进来,target_link_libraries 把库文件链进来。个人习惯是先给路径定义一个变量,当然也可以直接写字面量。
# 定义第三方库根目录
set(OpenCV_DIR "D:/opencv/build")
# 查找配置
find_package(OpenCV REQUIRED)
# 头文件包含目录
include_directories(${OpenCV_INCLUDE_DIRS})
# 链接库
target_link_libraries(TestCMake PRIVATE ${OpenCV_LIBS})
动态库另外还有 DLL 的问题:dll 一般要么放在系统根目录,要么配置了环境变量,也可以在代码里把 dll 复制到当前项目输出路径,保证运行时找得到。
五、测试代码怎么写
头文件里做常规声明,源文件里引入 OpenCV 头文件即可。下面这段用一个 10000×10000 的浮点矩阵做示例,分别用动态地址操作法和 OpenCV 内置运算做对比计时。
#pragma once
#include <opencv2/core.hpp>
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
#include "TestCMake.h"
int main()
{
Mat A = Mat::zeros(10000, 10000, CV_32FC1);
// 创建一个 A 矩阵
for (int i = 0; i < A.rows; i++) {
for (int j = 0; j < A.cols; j++) {
A.at(i, j) = rand() % 100 / 100.f;
}
}
Mat B0, B1, B2;
B0 = A.clone();
B1 = A.clone();
B2 = A.clone();
// 动态地址操作法
double time0 = static_cast<double>(getTickCount());
for (int i = 0; i < A.rows; i++) {
for (int j = 0; j < A.cols; j++) {
B0.at(i, j) *= 2;
}
}
time0 = ((double)getTickCount() - time0) / getTickFrequency();
cout << "dynamic address cost: " << time0 << endl;
// 矩阵运算重载法
time0 = static_cast<double>(getTickCount());
B1 = B1 * 2;
time0 = ((double)getTickCount() - time0) / getTickFrequency();
cout << "operator overload cost: " << time0 << endl;
return 0;
}
六、编译运行与排错
- 用
cmake -B build 生成,再用 Visual Studio 打开生成的解决方案编译。
- 报
fatal error: opencv2/core.hpp: No such file:说明 include_directories 没写对,检查头文件根目录。
- 报
LNK2019 unresolved external symbol:说明 target_link_libraries 里的库名或路径不对,或者库位数(x64/x86)与工程不一致。
- 运行时报找不到 opencv_worldXXX.dll:把 dll 所在目录加进 PATH,或复制到 exe 同目录。
- 换机器编译不过:优先检查
find_package 能否定位到 OpenCV 的 Config 文件,路径变了就重新指定。
七、小结
- 总项目管通用选项与子项目引入,子项目管目标与依赖,职责分清后配置就不乱。
- 接第三方库永远是“头文件目录 + 链接库”两步,缺一个都过不了。
- 动态库别忘了处理 dll 部署,静态库则一般不会遇到运行期缺库。
- 用内置算子替代逐元素循环,性能常常有数量级提升,测试代码里就能看出来。
qidao123.com企服评测-应用市场 编辑提示 |