淮安市城市建设档案馆网站,品牌策划大赛优秀作品,企业网站建设基本思路,温州的高端设计公司文章目录 1. 目的2. 设计整体思路多层依赖的处理获取 DLL 所在目录探测剩余的 DLL 文件 3. 代码实现判断 stack 是否为空判断 stack 是否为空获取所有 target检测并拷贝 DLL 4. 使用 1. 目的
在基于 CMake 构建的 C/C 工程中#xff0c;拷贝当前工程需要的每个DLL文件到 Visu… 文章目录 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 its 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 和 .dylibwindows 上的 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})