CMake:递归检查并拷贝所有需要的DLL文件
文章目录
- 1. 目的
- 2. 设计
- 整体思路
- 多层依赖的处理
- 获取 DLL 所在目录
- 探测剩余的 DLL 文件
- 3. 代码实现
- 判断 stack 是否为空
- 判断 stack 是否为空
- 获取所有 target
- 检测并拷贝 DLL
- 4. 使用

1. 目的
在基于 CMake 构建的 C/C++ 工程中,拷贝当前工程需要的每个DLL文件到 Visual Studio 工程的启动路径下, 让可执行目标在运行阶段正常运行,解决“DLL找不到”导致的程序终止、异常退出等问题,解决“每次都要手动拷贝,有时候忘记拷贝”的问题。
举例:
- OpenCV: 官方预编译版本,包含的 opencv_world.dll, 以及读取某些视频时需要的 opencv_ffmpeg dll 文件
- windows-pthreads 的 DLL 文件
- 其他依赖库当中,提供的 DLL 文件
实际上不仅限于 Windows 平台的 DLL, 在 Linux / MacOSX 上也同样有这样的问题,此时只需要增加 .so
和 .dylib
文件后缀的动态库支持即可。
本文给出基于 CMake 语言的解决方案。
2. 设计
整体思路
枚举所有 target, 筛选出 SHARED_LIBRARRY
类型的 target, 获取它们的动态库的路径 shared_library_path
, 然后拷贝到用户指定的目录 dstDir
.
此外有些 dll 文件并没有被 xxx-config.cmake 等配置文件所配置, 需要额外扫描和拷贝,例如 opencv 预编译库中的 ffmpeg 的 dll 文件。
多层依赖的处理
有时候工程比较复杂, target 至少包括三层, 最后一层是可执行文件, 第二层可能没有DLL,但第二层依赖的第一层则可能存在DLL文件,这就导致枚举 target 时不能只枚举一层。换言之,枚举 target 的过程是一个递归过程, 需要使用深度优先搜索 DFS 算法。
获取 DLL 所在目录
这个问题比较简单, 用 cmake 的 get_target_property
函数获取。
探测剩余的 DLL 文件
包括两种情况:
- target 本身是动态库类型, 那么它的 DLL 文件所在的目录应该被扫描,扫描出的新的 DLL 文件也应该被拷贝
- target 本身是静态库类型, 但它所在目录的上一级目录中, 有一个
bin
目录,bin
目录里存放有 DLL 文件
3. 代码实现
代码实现过程中遇到一些“难点”,主要是对 cmake 不够足够熟悉, 简单列举:
判断 stack 是否为空
- DFS 算法的实现过程中, 怎样判断 stack 为空?获取 stack 首部元素?依赖于对
list
的操作, 包括将“列表是否为空”封装为函数
#======================================================================
# Determine if a list is empty
#======================================================================
# Example:
# cvpkg_is_list_empty(testbed_requires testbed_requires_empty)
# message(STATUS "testbed_requires_empty: ${testbed_requires_empty}")
#----------------------------------------------------------------------
function(cvpkg_is_list_empty the_list ret)list(LENGTH ${the_list} the_list_length)if(${the_list_length} EQUAL 0)set(${ret} TRUE PARENT_SCOPE)else()set(${ret} FALSE PARENT_SCOPE)endif()
endfunction()
判断 stack 是否为空
通过判断元素是否在列表中来实现。封装为了函数
#======================================================================
# Determine if item is in the list
#======================================================================
# Example:
# cvpkg_is_item_in_list(testbed_requires "protobuf" protobuf_in_the_lst)
# message(STATUS "protobuf_in_the_lst: ${protobuf_in_the_lst}")
#
# cvpkg_is_item_in_list(testbed_requires "opencv" opencv_in_the_lst)
# message(STATUS "opencv_in_the_lst: ${opencv_in_the_lst}")
#----------------------------------------------------------------------
function(cvpkg_is_item_in_list the_list the_item ret)list(FIND ${the_list} ${the_item} index)if(index EQUAL -1)set(${ret} FALSE PARENT_SCOPE)else()set(${ret} TRUE PARENT_SCOPE)endif()
endfunction()
获取所有 target
原本的依赖关系是 hierarchical 的, 怎样拍平,得到一维的依赖列表?并且不能有重复元素?答案是用 DFS。
#======================================================================
# 4. Recursively get required packages for a package. No duplicated.
#======================================================================
# Example:
# cvpkg_get_flatten_requires(testbed flatten_pkgs)
# message(STATUS "flatten_pkgs: ${flatten_pkgs}")
#----------------------------------------------------------------------
function(cvpkg_get_flatten_requires input_pkg the_result)list(LENGTH input_pkg input_pkg_length)if(NOT (${input_pkg_length} EQUAL 1))cvpkg_error("input_pkg should be single element list")endif()set(visited_pkgs "")set(pkg_stack ${input_pkg})while(TRUE)cvpkg_is_list_empty(pkg_stack pkg_stack_empty)if(${pkg_stack_empty})break()endif()cvpkg_debug("pkg_stack: ${pkg_stack}")# pop the last elementlist(POP_BACK pkg_stack pkg)cvpkg_debug("pkg: ${pkg}")# mark the element as visitedcvpkg_is_item_in_list(visited_pkgs "${pkg}" pkg_visited)if(NOT ${pkg_visited})cvpkg_debug(" visiting ${pkg}")list(APPEND visited_pkgs ${pkg})# traverse it's required dependencies and put into pkg_stackget_target_property(subpkgs ${pkg} LINK_LIBRARIES)cvpkg_debug("LINK_LIBRARIES: ${subpkgs}")if(subpkgs)foreach(subpkg ${subpkgs})if(TARGET ${subpkg}) # if called target_link_libraries() more than once, subpkgs contains stuffs like `::@(000001FAFA8C75C0)`cvpkg_debug(" subpkg: ${subpkg}")list(APPEND pkg_stack ${subpkg})endif()endforeach()endif()get_target_property(subpkgs ${pkg} INTERFACE_LINK_LIBRARIES)cvpkg_debug("INTERFACE_LINK_LIBRARIES: ${subpkgs}")if(subpkgs)foreach(subpkg ${subpkgs})if(TARGET ${subpkg}) # if called target_link_libraries() more than once, subpkgs contains stuffs like `::@(000001FAFA8C75C0)`cvpkg_debug(" subpkg: ${subpkg}")list(APPEND pkg_stack ${subpkg})endif()endforeach()endif()endif()endwhile()list(POP_FRONT visited_pkgs visited_pkgs)set(${the_result} ${visited_pkgs} PARENT_SCOPE)
endfunction()
检测并拷贝 DLL
这是代码最多的函数, 不过思路上前面已经提到过, 并不复杂。
代码多的几个原因:
- 支持 .dll 的同时, 要支持 .so 和 .dylib
- windows 上的 target, 可能 debug 和 release 库的文件不是同一个,都需要拷贝,因此需要枚举5个属性
set(prop_lst "IMPORTED_LOCATION;IMPORTED_LOCATION_DEBUG;IMPORTED_LOCATION_RELEASE")
- 去重: 拷贝过的文件要忽略, 重复的目录要合并
Talk is cheap, show me the code:
#======================================================================
# Copy imported lib for all build types
# Should only be used for shared libs, e.g. .dll, .so, .dylib
#======================================================================
# Example:
# cvpkg_copy_imported_lib(testbed ${CMAKE_BINARY_DIR}/${testbed_output_dir})
#----------------------------------------------------------------------
function(cvpkg_copy_imported_lib targetName dstDir)set(prop_lst "IMPORTED_LOCATION;IMPORTED_LOCATION_DEBUG;IMPORTED_LOCATION_RELEASE")if(NOT (TARGET ${targetName}))return()endif()if(CMAKE_SYSTEM_NAME MATCHES "Windows")set(shared_library_filename_ext ".dll")elseif(CMAKE_SYSTEM_NAME MATCHES "Linux")set(shared_library_filename_ext ".so")elseif(CMAKE_SYSTEM_NAME MATCHES "Darwin")set(shared_library_filename_ext ".dylib")endif()get_target_property(pkg_type ${targetName} TYPE)if(NOT (${pkg_type} STREQUAL "SHARED_LIBRARY"))if(${pkg_type} STREQUAL "STATIC_LIBRARY")if(CMAKE_SYSTEM_NAME MATCHES "Windows")set(static_library_filename_ext ".lib")elseif(CMAKE_SYSTEM_NAME MATCHES "Linux")set(static_library_filename_ext ".a")elseif(CMAKE_SYSTEM_NAME MATCHES "Darwin")set(static_library_filename_ext ".a")endif()### for static library targets, there might be `bin` directory, parallel to `lib` directory.# 先获取静态库文件路径foreach(prop ${prop_lst})get_target_property(static_library_path ${pkg} ${prop})if(static_library_path)# 获取静态库所在目录get_filename_component(static_library_live_directory ${static_library_path} DIRECTORY)# 获取静态库目录的上层目录get_filename_component(static_library_parent_directory ${static_library_live_directory} DIRECTORY)set(candidate_bin_dir "${static_library_parent_directory}/bin")# 判断上层目录是否存在 bin 目录, 如果存在 bin 目录, 执行扫描和拷贝if(EXISTS "${candidate_bin_dir}")set(glob_pattern "${candidate_bin_dir}/*${shared_library_filename_ext}")file(GLOB shared_library_path_lst "${glob_pattern}")foreach(shared_library_path ${shared_library_path_lst})list(APPEND copied_shared_library_path_lst "${shared_library_path}")cvpkg_info("Copy ${shared_library_filename_ext} file (for static library, we detect and copy them!)")cvpkg_info(" - shared library file: ${prop}=${static_library_path}")cvpkg_info(" - dstDir: ${dstDir}")execute_process(COMMAND ${CMAKE_COMMAND} -E copy ${shared_library_path} ${dstDir})endforeach()endif()endif()endforeach()endif()return()endif()### copy as the package description file (xxx-config.cmake or xxx.cmake) decribedset(pkg ${targetName})set(copied_shared_library_path_lst "")foreach(prop ${prop_lst})cvpkg_debug("!! prop: ${prop}")get_target_property(shared_library_path ${pkg} ${prop})if(shared_library_path)list(APPEND copied_shared_library_path_lst "${shared_library_path}")cvpkg_info("Copy ${shared_library_filename_ext} file")cvpkg_info(" - package(target): ${pkg}")cvpkg_info(" - prop: ${prop}=${shared_library_path}")cvpkg_info(" - dstDir: ${dstDir}")execute_process(COMMAND ${CMAKE_COMMAND} -E copy ${shared_library_path} ${dstDir})endif()endforeach()### copy un-tracked shared library files that under same directory of each tracked shared library filescvpkg_is_list_empty(copied_shared_library_path_lst copied_shared_library_path_lst_empty)if(${copied_shared_library_path_lst_empty})return()endif()# get directories of each copied shared library filesset(shared_library_live_directory_lst "")foreach(copied_shared_library_path ${copied_shared_library_path_lst})get_filename_component(shared_library_live_directory ${copied_shared_library_path} DIRECTORY)list(APPEND shared_library_live_directory_lst "${shared_library_live_directory}")endforeach()# remove duplicated directorieslist(REMOVE_DUPLICATES "${shared_library_live_directory_lst}")# for each candidate directory, scan shared library filesforeach(shared_library_live_directory ${shared_library_live_directory_lst})set(glob_pattern "${shared_library_live_directory}/*${shared_library_filename_ext}")file(GLOB shared_library_path_lst "${glob_pattern}")foreach(shared_library_path ${shared_library_path_lst})# if the scanned shared library file is not copied, do a copycvpkg_is_item_in_list(copied_shared_library_path_lst "${shared_library_path}" shared_library_already_copied)if(NOT shared_library_already_copied)list(APPEND copied_shared_library_path_lst "${shared_library_path}")cvpkg_info("Copy ${shared_library_filename_ext} file (xxx-config.cmake forget this file, but we copy them!)")cvpkg_info(" - package(target): ${pkg}")cvpkg_info(" - prop: ${prop}=${shared_library_path}")cvpkg_info(" - dstDir: ${dstDir}")execute_process(COMMAND ${CMAKE_COMMAND} -E copy ${shared_library_path} ${dstDir})endif()endforeach()endforeach()endfunction()
4. 使用
从使用的角度非常简单:调用 cvpkg_copy_required_dlls()
函数即可,它的实现代码为:
#======================================================================
# Recursively copy required DLL files into destination directory
#======================================================================
# Example:
# cvpkg_copy_required_dlls(testbed ${CMAKE_BINARY_DIR})
# cvpkg_copy_required_dlls(testbed ${CMAKE_BINARY_DIR}/${testbed_output_dir})
#----------------------------------------------------------------------
function(cvpkg_copy_required_dlls targetName dstDir)cvpkg_get_flatten_requires(testbed flatten_pkgs)#cvpkg_debug("flatten_pkgs: ${flatten_pkgs}")message(STATUS "flatten_pkgs: ${flatten_pkgs}")foreach(pkg ${flatten_pkgs})cvpkg_copy_imported_lib(${pkg} ${dstDir})endforeach()
endfunction()
调用代码为:
cvpkg_copy_required_dlls(testbed ${CMAKE_BINARY_DIR})
相关文章:

CMake:递归检查并拷贝所有需要的DLL文件
文章目录 1. 目的2. 设计整体思路多层依赖的处理获取 DLL 所在目录探测剩余的 DLL 文件 3. 代码实现判断 stack 是否为空判断 stack 是否为空获取所有 target检测并拷贝 DLL 4. 使用 1. 目的 在基于 CMake 构建的 C/C 工程中,拷贝当前工程需要的每个DLL文件到 Visu…...
python常见问题及解决方案
Python是一种高级编程语言,具有易于学习、易于阅读和易于维护的特点。然而,即使是最有经验的Python开发人员也可能会遇到一些常见的错误。在本文中,我们将讨论一些常见的Python运行时错误,并提供解决这些错误的办法。 语法错误 …...
JUC之Synchronized与Lock
Synchronized 称之为”同步锁 作用: 保证在同一时刻, 被修饰的代码块或方法只会有一个线程执行,以达到保证并发安全的效果 用法: 1.修饰方法:方法锁,锁的对象是当前对象 2.修饰静态方法:类锁…...
动态规划理论基础
文章目录 定义动态规划与分治问题的区别两种方式实现动态规划方法一:带备忘录的自顶向下法方法二:自底向上法 本质核心解题步骤常见题型划分 定义 动态规划方法通常用来求解最优化问题(optimization problem)。这类问题可以有很多可行解,每个…...

Redis的数据类型
参考文档:https://www.runoob.com/redis/redis-tutorial.html redis当中一共支持五种数据类型,分别是: string字符串 list列表 set集合 hash表 zset有序集合 1、对字符串string的操作 下表列出了常用的 redis 字符串命令 1 设置值 获取…...

vue3鼠标经过显示按钮
在前端开发中,我们经常需要在页面中添加一些交互效果来提升用户体验。其中一个常见的需求就是鼠标经过某个元素时显示一个按钮,这个按钮可以用于触发一些操作或者显示更多的内容。 在本篇文章中,我将会介绍如何使用 Vue3 实现一个鼠标经过显…...
【2023华为OD笔试必会25题--C语言版】《18 最短木板长度》——数组
本专栏收录了华为OD 2022 Q4和2023Q1笔试题目,100分类别中的出现频率最高(至少出现100次)的25道,每篇文章包括原始题目 和 我亲自编写并在Visual Studio中运行成功的C语言代码。 仅供参考、启发使用,切不可照搬、照抄,查重倒是可以过,但后面的技术面试还是会暴露的。✨✨…...
yolov5车道线检测+测距(碰撞检测)
yolov5车道线检测+测距(碰撞检测) 1. 车道线检测2. 测距2.1 测距原理2.2 相机标定2.2.1:标定方法12.2.2:标定方法23. 相机测距3.1 测距添加3.2 主代码4. 实验结果相关链接 1. 基于yolov5的车道线检测及安卓部署 2. YOLOv5+单目测距(python) 3. 具体实现效果...
微服务学习笔记--(Gateway网关)
统一网关Gateway 为什么需要网关gateway快速入门断言工厂过滤器工厂全局过滤器跨域问题 Gateway网关-网关作用介绍 为什么需要网关 网关功能: 身份认证和权限校验服务路由、负载均衡请求限流 网关的技术实现 在SpringCloud中网关的实现包括两种: …...
QML插件的创建及调用
QML插件的创建及调用 创建QML Plugin注册插件调用插件 创建QML Plugin 1、 注册插件 1、可以将qml文件放在qmldir中进行声明。 此种方式需要将qml文件和qmldir放在一起 module EularFrame plugin EularFrameEButton 1.0 MyButton.qml2、可以在*plugin.cpp注册 此种方式只需…...

数据结构学习分享之树的介绍
💓博主CSDN主页:杭电码农-NEO💓 ⏩专栏分类:数据结构学习分享⏪ 🚚代码仓库:NEO的学习日记🚚 🌹关注我🫵带你了解更多数据结构的知识 🔝🔝 数据结构第六课 1. 前言&a…...

MySQL数据库基础2
文章目录 数据类型表的约束 数据类型 1、数值类型:BIT、TINYINT、BOOL、SMALLINT、INT、BIGINT、FLOAT[(M,D)]、DOUBLE[(M,D)]、DECIMAL[(M,D)] FLOAT[(M,D)]:占用四个字节,M表示显示位数,D表示小数位数,精度保证&am…...

AutoSAR PNC和ComM
文章目录 PNC和ComMPNC管理NM PDU结构及PNC信息位置如何理解节点关联PNCPNC状态管理 ComM 通道状态管理 PNC和ComM PNC 和 ComM层的Channel不是一个概念,ComM的Channel对应具体的物理总线数。 在ComM模块中,一个Channel可以对应一个PNC,也可…...
Android studio Camera2实现的详细流程
流程 一、获取CameraManager实例二、获取可用的相机列表三、选择一个相机并打开它四、创建一个CaptureRequest.Builder对象五、设置CaptureRequest.Builder对象的参数六、创建一个CaptureSession对象七、开始预览 代码示例 一、获取CameraManager实例 CameraManager manager (…...

阿里云数据库ClickHouse产品和技术解读
摘要:社区ClickHouse的单机引擎性能十分惊艳,但是部署运维ClickHouse集群,以及troubleshoot都不是很好上手。本次分享阿里云数据库ClickHouse产品能力和特性,包含同步MySQL库、ODPS库、本地盘及多盘性价比实例以及自建集群上云的迁…...

分子动力学基础知识
分子动力学基础知识 目前主要存在两种基本模型:其一为量子统计力学, 其二为经典统计力学。 量子统计力学 基于量子力学原理, 适用 于微观的, 小尺度, 短时 间的模拟,可以描述电子 的结构分布,原子间的成 键断键等化学性质。 经典纭计力学…...

USB转UART转串口芯片 GP232RNL国产低成本替代FT232RL/FT232RNL
近期收到很多人咨询FT232RL跟新版FT232RNL两者有什么区别,实际上就是内部做了一点升级,FT232RNL支持Windows11系统,参数并没有改动,完全可以直接替换使用。 今天小编给大家讲讲FT232RNL国产低成本替代芯片–GP232RNL GP232RNL 是…...

第03讲:SpringCloudStream实现分布式事务
需求分析 本案例是通过一个发送短信验证码的功能来实验MQ发送消息时实现分布式事务,思路分析如下 消息生产者生产发送验证码的半消息 生产者执行本地事务(将验证码保存到数据库),并记录事务的ID,如果整个过程不出现异…...

【从零开始学Skynet】高级篇(一):Protobuf数据传输
1、什么是Protobuf Protobuf是谷歌发布的一套协议格式,它规定了一系列的编码和解 码方法,比如对于数字,它要求根据数字的大小选择存储空间,小于等于15的数字只用1个字节来表示,大于15的数用2个字节表示,以此…...
快速入门Lombok
Lombok是一个Java库,可以通过注解的方式来简化Java代码,它可以自动生成Getter、Setter、构造函数等代码,从而减少重复的模板代码。下面是Lombok的使用详情: 1. 添加Lombok依赖 在使用Lombok之前,我们需要先添加Lombo…...

linux之kylin系统nginx的安装
一、nginx的作用 1.可做高性能的web服务器 直接处理静态资源(HTML/CSS/图片等),响应速度远超传统服务器类似apache支持高并发连接 2.反向代理服务器 隐藏后端服务器IP地址,提高安全性 3.负载均衡服务器 支持多种策略分发流量…...
在HarmonyOS ArkTS ArkUI-X 5.0及以上版本中,手势开发全攻略:
在 HarmonyOS 应用开发中,手势交互是连接用户与设备的核心纽带。ArkTS 框架提供了丰富的手势处理能力,既支持点击、长按、拖拽等基础单一手势的精细控制,也能通过多种绑定策略解决父子组件的手势竞争问题。本文将结合官方开发文档,…...

第 86 场周赛:矩阵中的幻方、钥匙和房间、将数组拆分成斐波那契序列、猜猜这个单词
Q1、[中等] 矩阵中的幻方 1、题目描述 3 x 3 的幻方是一个填充有 从 1 到 9 的不同数字的 3 x 3 矩阵,其中每行,每列以及两条对角线上的各数之和都相等。 给定一个由整数组成的row x col 的 grid,其中有多少个 3 3 的 “幻方” 子矩阵&am…...

tree 树组件大数据卡顿问题优化
问题背景 项目中有用到树组件用来做文件目录,但是由于这个树组件的节点越来越多,导致页面在滚动这个树组件的时候浏览器就很容易卡死。这种问题基本上都是因为dom节点太多,导致的浏览器卡顿,这里很明显就需要用到虚拟列表的技术&…...

零基础在实践中学习网络安全-皮卡丘靶场(第九期-Unsafe Fileupload模块)(yakit方式)
本期内容并不是很难,相信大家会学的很愉快,当然对于有后端基础的朋友来说,本期内容更加容易了解,当然没有基础的也别担心,本期内容会详细解释有关内容 本期用到的软件:yakit(因为经过之前好多期…...
《C++ 模板》
目录 函数模板 类模板 非类型模板参数 模板特化 函数模板特化 类模板的特化 模板,就像一个模具,里面可以将不同类型的材料做成一个形状,其分为函数模板和类模板。 函数模板 函数模板可以简化函数重载的代码。格式:templa…...
音视频——I2S 协议详解
I2S 协议详解 I2S (Inter-IC Sound) 协议是一种串行总线协议,专门用于在数字音频设备之间传输数字音频数据。它由飞利浦(Philips)公司开发,以其简单、高效和广泛的兼容性而闻名。 1. 信号线 I2S 协议通常使用三根或四根信号线&a…...
Java求职者面试指南:计算机基础与源码原理深度解析
Java求职者面试指南:计算机基础与源码原理深度解析 第一轮提问:基础概念问题 1. 请解释什么是进程和线程的区别? 面试官:进程是程序的一次执行过程,是系统进行资源分配和调度的基本单位;而线程是进程中的…...

【C++特殊工具与技术】优化内存分配(一):C++中的内存分配
目录 一、C 内存的基本概念 1.1 内存的物理与逻辑结构 1.2 C 程序的内存区域划分 二、栈内存分配 2.1 栈内存的特点 2.2 栈内存分配示例 三、堆内存分配 3.1 new和delete操作符 4.2 内存泄漏与悬空指针问题 4.3 new和delete的重载 四、智能指针…...

【Linux】Linux 系统默认的目录及作用说明
博主介绍:✌全网粉丝23W,CSDN博客专家、Java领域优质创作者,掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域✌ 技术范围:SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大数据、物…...