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 常见命令与常见问题解决思路
Linux 常见命令 Linux 基础命令目录相关查看文件(日志)查看普通的文件查看压缩的文件 解压压缩Linux 系统调优topvmstatpidstatps vi/vim 编辑文件查找文件属性相关定时任务scp 复制文件和目录awk 分隔cutsort 与 uniq常见问题处理思路CPU 高系统平均负载…...

用GPT-4 写2022年天津高考作文能得多少分?
正文共 792 字,阅读大约需要 3 分钟 学生必备技巧,您将在3分钟后获得以下超能力: 积累作文素材 Beezy评级 :B级 *经过简单的寻找, 大部分人能立刻掌握。主要节省时间。 推荐人 | Kim 编辑者 | Linda ●图片由Lexica …...

Django如何把SQLite数据库转换为Mysql数据库
大部分新手刚学Django开发的时候默认用的都是SQLite数据库,上线部署的时候,大多用的却是Mysql。那么我们应该如何把数据库从SQLite迁移转换成Mysql呢? 之前我们默认使用的是SQLite数据库,我们开发完成之后,里面有许多数…...

使用apisix代理静态文件
前言 最近公司考虑用apisix作为公司网关并且部署到k8s上,我这边收到一个小任务:使用apisix代理静态文件 通过apisix官网了解到它构建于 NGINX ngx_lua 的技术基础之上,所以按理应该和nginx代理静态资源是一样的。因为是通过docker容器部署…...

[元带你学NVMe协议] NVMe1.4 多路径(Multipathing)
声明 主页:元存储的博客_CSDN博客 依公开知识及经验整理,如有误请留言。 个人辛苦整理,付费内容,禁止转载。 内容摘要 全文9100字, 主要内容 目录 前言 1 多路径(Multipathing)概念...

Elasticsearch:如何使用自定义的证书安装 Elastic Stack 8.x
在我之前的文章 “如何在 Linux,MacOS 及 Windows 上进行安装 Elasticsearch”,我详细描述了如何在各个平台中安装 Elastic Stack 8.x。在其中的文章中,我们大多采用默认的证书来安装 Elasticsearch。在今天的文章中,我们用自己创…...

HADOOP--yarn ,, git
Yarn架构体系 主从架构 也是采用 master(Resource Manager)- slave (Node Manager)架构,Resource Manager 整个集群只有一个,一个可靠的节点。 1、 每个节点上可以负责该节点上的资源管理以及任务调度&am…...

IOS开发指南之UITableView控件使用
1.创建一个IOS单页应用 2.双击Main.storyboard然后拖放UITableView到视图中 3.添加TableViewCell 成功添加Table View Cell 4.修改Table View Cell属性 选中Table View Cell 在右边的Image栏输入default.png回车 到此布局设计完成,现在运行还是显示 空白,要在代码中做相关的实…...
C语言中的数据类型
目录 一、数据类型 1.基本类型 2.sizeof运算符 3.signed和unsigned 二、基本数据类型的取值范围 1.比特位 2.字节 3.符号位 4.补码 5.基本数据类型的取值范围 一、数据类型 1.基本类型 (1)整数类型 short intintlong intlong long int &…...

什么是微服务中的熔断器设计模式?
在本文中,我将解释什么是熔断器设计模式以及它解决了什么问题。 我们将仔细研究熔断器设计模式,并探讨如何使用Spring Cloud Netflix Hystrix在Java中实现它。到本文结束时,您将更好地了解如何使用熔断器设计模式提高微服务架构的弹性。 熔断…...