如何使用CMake将DLL文件复制到与可执行文件相同的文件夹中?

Mat*_*Mat 85 dll cmake

我们使用CMake在我们的SVN中生成源代码的Visual Studio文件.现在我的工具需要一些DLL文件与可执行文件位于同一文件夹中.DLL文件位于源旁边的文件夹中.

如何更改我CMakeLists.txt的生成的Visual Studio项目将在发布/调试文件夹中已经具有特定的DLL文件,或者在编译时将它们复制?

Fra*_*ser 112

我会用它add_custom_command来实现这个目标cmake -E copy_if_different....完整信息运行

cmake --help-command add_custom_command
cmake -E
Run Code Online (Sandbox Code Playgroud)


所以在你的情况下,如果你有以下目录结构:

/CMakeLists.txt
/src
/libs/test.dll
Run Code Online (Sandbox Code Playgroud)

以及该命令适用的CMake目标MyTest,然后您可以将以下内容添加到CMakeLists.txt:

add_custom_command(TARGET MyTest POST_BUILD        # Adds a post-build event to MyTest
    COMMAND ${CMAKE_COMMAND} -E copy_if_different  # which executes "cmake - E copy_if_different..."
        "${PROJECT_SOURCE_DIR}/libs/test.dll"      # <--this is in-file
        $<TARGET_FILE_DIR:MyTest>)                 # <--this is out-file path
Run Code Online (Sandbox Code Playgroud)


如果您只想/libs/复制目录的全部内容,请使用cmake -E copy_directory:

add_custom_command(TARGET MyTest POST_BUILD
    COMMAND ${CMAKE_COMMAND} -E copy_directory
        "${PROJECT_SOURCE_DIR}/libs"
        $<TARGET_FILE_DIR:MyTest>)
Run Code Online (Sandbox Code Playgroud)


如果您需要根据配置(Release,Debug等)复制不同的dll,那么您可以在以相应配置命名的子目录中使用:/libs/Release/libs/Debug.然后,您需要将配置类型注入到add_custom_command调用中dll的路径中,如下所示:

add_custom_command(TARGET MyTest POST_BUILD
    COMMAND ${CMAKE_COMMAND} -E copy_directory
        "${PROJECT_SOURCE_DIR}/libs/$<CONFIGURATION>"
        $<TARGET_FILE_DIR:MyTest>)
Run Code Online (Sandbox Code Playgroud)

  • 快速说明我的案例中有什么用,以防将来帮助其他人:我有一个静态库项目,主要可执行文件可选择链接,并且该库需要复制DLL(如果添加).因此,在该库的CMakeLists.txt文件中,我使用`$ {CMAKE_RUNTIME_OUTPUT_DIRECTORY}/$ <CONFIG>`作为目标目标.否则它会将它复制到库构建路径,这是无用的. (2认同)
  • `$&lt;TARGET_FILE_DIR:MyTest&gt;` - 它是什么?如何打印信息,究竟是什么意思 (2认同)
  • 在测试并启动我的cmake时:如果使用的是“导入的”库,并且需要重新定位DLL,则需要使用变体命令。如果您将“导入的”库添加为“ MyImportedLib”,则将使用“ COMMAND $ {CMAKE_COMMAND} -E copy_if_different $ &lt;TARGET_FILE:MyImportedLib&gt; $ &lt;TARGET_FILE_DIR:MyTest&gt;`请注意,要运行多个构建后命令,您需要将它们全部绑定到一个自定义命令中,例如```add_custom_command(TARGET MyTest POST_BUILD COMMAND#您的第一个命令#COMMAND#您的第二个命令#)`'' (2认同)

Gni*_*muc 27

对于 Windows 用户,CMake 3.21+ 中有一个新的生成器表达式$<TARGET_RUNTIME_DLLS:tgt>,您可以使用此官方代码片段来复制目标所依赖的所有 DLL。

find_package(foo REQUIRED)

add_executable(exe main.c)
target_link_libraries(exe PRIVATE foo::foo foo::bar)
add_custom_command(TARGET exe POST_BUILD
  COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_RUNTIME_DLLS:exe> $<TARGET_FILE_DIR:exe>
  COMMAND_EXPAND_LISTS
  )
Run Code Online (Sandbox Code Playgroud)


sor*_*bet 22

1.最正确的方法:install(TARGET_RUNTIME_DLLS)(CMake >= 3.21)

install(FILES $<TARGET_RUNTIME_DLLS:your_exe_here> TYPE BIN)
Run Code Online (Sandbox Code Playgroud)

为此,您的依赖项的 CMake 模块必须编写良好。换句话说,他们使用 CMake 3 目标,并正确设置所有目标属性。如果他们设置一切正确,您的所有 DLL 将自动收集并与您的 exe 一起安装。CMake 将自动匹配 DLL 类型(例如发布与调试)以匹配您的目标 exe。

这就是 CMake 未来的发展方向,如果您有选择的话,您应该更喜欢这种方式。

您应该更喜欢install()直接将文件复制到,$CMAKE_RUNTIME_OUTPUT_DIRECTORY因为install()这是官方 CMake 认可的将内容放入$CMAKE_RUNTIME_OUTPUT_DIRECTORY. install()之所以命名如此奇怪,是因为 CMake 不仅仅是一个构建工具。它也是一个安装程序生成工具。此安装程序生成功能称为 CPack。在CMake眼中,$CMAKE_RUNTIME_OUTPUT_DIRECTORY只是CPack的存放区域。当您install()创建文件时,您告诉 CMake 它应该被视为程序文件,无论 exe 所在的位置都可以复制。如果您不执行install(),CMake 会将其视为随机的无法识别的文件。

2.第二个最正确的方法:install(RUNTIME_DEPENDENCIES)(CMake >= 3.21)

install(TARGETS your_exe_here
    RUNTIME ARCHIVE LIBRARY RUNTIME FRAMEWORK BUNDLE PUBLIC_HEADER RESOURCE)
install(TARGETS your_exe_here
    COMPONENT your_exe_here
    RUNTIME_DEPENDENCIES
    PRE_EXCLUDE_REGEXES "api-ms-" "ext-ms-"
    POST_EXCLUDE_REGEXES ".*system32/.*\\.dll"
    DIRECTORIES $<TARGET_FILE_DIR:your_exe_here>)
Run Code Online (Sandbox Code Playgroud)

这里的关键是RUNTIME_DEPENDENCIES

在内部,RUNTIME_DEPENDENCIES调用会file(GET_RUNTIME_DEPENDENCIES)扫描可执行二进制文件,努力准确地复制实际的依赖关系解析的样子,并记下沿途提到的所有 DLL。这些被传递回install().

这意味着这不依赖于您的依赖项的 CMake 模块是否正确设置了目标属性。您的实际可执行二进制文件已被扫描。一切都会被拾起。

3.第三个最正确的方法:file(GET_RUNTIME_DEPENDENCIES)(CMake >= 3.16)

file(GET_RUNTIME_DEPENDENCIES)install(RUNTIME_DEPENDENCIES)在幕后调用的,但file(GET_RUNTIME_DEPENDENCIES)可以在比install(RUNTIME_DEPENDENCIES). 我们仍然可以在旧的 CMake 版本中做同样的事情,只是使用更多的样板文件。

棘手的部分是file(GET_RUNTIME_DEPENDENCIES)只能在安装时调用。这意味着我们需要使用install(CODE)来运行一个依次调用file(GET_RUNTIME_DEPENDENCIES). 有关实现,请参阅此处

4. 最后的手段:install(DIRECTORY)

install(
  DIRECTORY "${DIR_CONTAINING_YOUR_DLLS}"
  TYPE BIN
  FILES_MATCHING REGEX "[^\\\\/.]\\.[dD][lL][lL]$"
)
Run Code Online (Sandbox Code Playgroud)

要使用,请将适合您的构建的 DLL 放入$DIR_CONTAINING_YOUR_DLLS.

这里的技巧是,与 不同install(FILES)install(DIRECTORY)在安装之前并不关心目录中有哪些特定文件。这意味着现在我们有足够的配置时间和编译时间来获取 DLL 列表并将它们填充到$DIR_CONTAINING_YOUR_DLLS. 只要 DLL 文件$DIR_CONTAINING_YOUR_DLLS在安装时存在,install(DIRECTORY)就会拾取它们。

如果您选择此方法,则您有责任将 DLL 与您的构建配置相匹配。(考虑:静态与动态、调试与发布、导入库版本与 DLL 版本、具有可选多线程的库、忘记删除不再需要的 DLL。)

如果您选择此方法,您可能希望使用vcpkg诸如applocal.ps1 does. (假设,应该可以使用 来重新实现纯 CMake 中的功能vcpkg,但我没有准备好发布的实现。)applocal.ps1install(CODE)


提示vcpkg

如果您使用vpckg启用VCPKG_APPLOCAL_DEPSvcpkg将找到您的 DLL 并将其复制到您的 DLL 中$CMAKE_RUNTIME_OUTPUT_DIRECTORY,但无需通过install(). 您需要使用install(DIRECTORY)技巧让 CMake 来识别它们。

(在内部,vcpkg使用dumpbinllvm-objdumpobjdump扫描可执行二进制文件以获取这些文件名。)


Dav*_*son 20

我把这些行放在我的顶级CMakeLists.txt文件中.由CMake编译的所有库和可执行文件都将放置在构建目录的顶层,以便可执行文件可以找到库并且很容易运行所有内容.

set (CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
set (CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
Run Code Online (Sandbox Code Playgroud)

请注意,这并不能解决OP从项目源目录复制预编译二进制文件的问题.


Min*_*eat 13

我今天在尝试为我的程序制作 Windows 版本时遇到了这个问题。我最终自己做了一些研究,因为所有这些答案都没有让我满意。主要存在三个问题:

  • 我希望调试版本与库的调试版本链接,发布版本分别与库的发布版本链接。

  • 除此之外,我希望将正确版本的 DLL 文件(调试/发布)复制到输出目录。

  • 我想在不编写复杂和脆弱的脚本的情况下实现这一切。

在 github 浏览了一些 CMake 手册和一些多平台项目后,我找到了这个解决方案:

将您的库声明为具有“IMPORTED”属性的目标,引用其调试和发布 .lib 和 .dll 文件。

add_library(sdl2 SHARED IMPORTED GLOBAL)
set_property(TARGET sdl2 PROPERTY IMPORTED_IMPLIB_RELEASE "${SDL_ROOT_PATH}/lib/SDL2.lib")
set_property(TARGET sdl2 PROPERTY IMPORTED_LOCATION_RELEASE "${SDL_ROOT_PATH}/bin/SDL2.dll")
set_property(TARGET sdl2 PROPERTY IMPORTED_IMPLIB_DEBUG "${SDL_ROOT_PATH}/lib/SDL2d.lib")
set_property(TARGET sdl2 PROPERTY IMPORTED_LOCATION_DEBUG "${SDL_ROOT_PATH}/bin/SDL2d.dll")
Run Code Online (Sandbox Code Playgroud)

像往常一样将此目标与您的项目联系起来

target_link_libraries(YourProg sdl2 ...)
Run Code Online (Sandbox Code Playgroud)

如果自上次构建以来以某种方式更改了 dll 文件,则进行自定义构建步骤以将 dll 文件复制到其目的地

add_custom_command ( TARGET YourProg POST_BUILD
    COMMAND ${CMAKE_COMMAND} -E copy_if_different
    $<TARGET_FILE:sdl2> $<TARGET_FILE_DIR:YourProg>
)
Run Code Online (Sandbox Code Playgroud)


小智 8

在构建期间移动文件使用 install

我在尝试遵循 CMake 官方教程的步骤 9时遇到了这个问题。这是我要移动的文件的位置:

src
 |_build
    |_Debug
       - `MathFunctions.dll`
Run Code Online (Sandbox Code Playgroud)

这是我想要文件所在的位置:

src
 |_build
    |_install
         |_bin
             - `MathFunctions.dll`
Run Code Online (Sandbox Code Playgroud)

由于此 DLL 是作为共享库生成的,因此我所做的就是将此行包含在包含该库源代码的子目录CMakeLists.txtsrc/Mathfunctions/CMakeLists.txt

install(FILES ${PROJECT_BINARY_DIR}/$<CONFIG>/MathFunctions.dll
DESTINATION bin)
Run Code Online (Sandbox Code Playgroud)

感谢您的回答,我可以思考这个问题。只有一行,所以我认为还可以。可以$<CONFIG>有两个值“调试”“发布”,具体取决于项目的构建方式,如原始问题所需。


Dan*_*son 5

接受答案的附录,作为单独的答案添加,以便我获得代码格式:

如果要在同一个项目中构建dll,它们通常位于Release,Debug等目录中.您必须使用Visual Studio环境变量来正确复制它们.例如:

"${PROJECT_BINARY_DIR}/your_library/\$\(Configuration\)/your_library.dll"
Run Code Online (Sandbox Code Playgroud)

对于来源和

"${CMAKE_CURRENT_BINARY_DIR}/\$\(Configuration\)/your_library.dll"
Run Code Online (Sandbox Code Playgroud)

为目的地.注意逃跑!

您不能将CMake CMAKE_BUILD_TYPE变量用于配置,因为它在VS项目生成时解析,并且始终是默认值.

  • 接受的答案的最后一部分使用`$ <CONFIGURATION>`以更干净的CMake方式解决了这个问题 (7认同)

小智 5

You can also use the command find_library:

find_library(<some_var> NAMES <name_of_lib> PATHS "<path/to/lib>")
Run Code Online (Sandbox Code Playgroud)

With a defined EXECUTABLE_PATH, for instance:

set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
Run Code Online (Sandbox Code Playgroud)

you could move the .dll files that your executable need, with

file(COPY ${<some_var>}
    DESTINATION ${EXECUTABLE_OUTPUT_PATH})
Run Code Online (Sandbox Code Playgroud)