使用Visual Studio在CMake项目中链接第三方库(OpenCV为例)

[复制链接]
发表于 2026-10-3 22:50:00 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×

在 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;
}

六、编译运行与排错

  1. 用 cmake -B build 生成,再用 Visual Studio 打开生成的解决方案编译。
  2. 报 fatal error: opencv2/core.hpp: No such file:说明 include_directories 没写对,检查头文件根目录。
  3. 报 LNK2019 unresolved external symbol:说明 target_link_libraries 里的库名或路径不对,或者库位数(x64/x86)与工程不一致。
  4. 运行时报找不到 opencv_worldXXX.dll:把 dll 所在目录加进 PATH,或复制到 exe 同目录。
  5. 换机器编译不过:优先检查 find_package 能否定位到 OpenCV 的 Config 文件,路径变了就重新指定。

七、小结

  • 总项目管通用选项与子项目引入,子项目管目标与依赖,职责分清后配置就不乱。
  • 接第三方库永远是“头文件目录 + 链接库”两步,缺一个都过不了。
  • 动态库别忘了处理 dll 部署,静态库则一般不会遇到运行期缺库。
  • 用内置算子替代逐元素循环,性能常常有数量级提升,测试代码里就能看出来。

qidao123.com企服评测-应用市场 编辑提示

回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表