成都解放号网站建设怎么网络推广自己业务
文章目录
- 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})