首页 > 解决方案 > CMake GET_RUNTIME_DEPENDENCIES通过lib(导入库)链接时找不到dll库?

问题描述

构建操作系统:Windows 10、Cmake 3.16.3。

target_link_libraries用来将第 3 方.lib文件链接到我的.dll库。

但是当我GET_RUNTIME_DEPENDENCIES用来安装我的dll时,没有找到依赖项。

它只发生在 Windows 上,在 Linux 上安装是可以的。

是否有任何线索如何解决这个问题,或者至少如何调试它?

什么确切的命令在 Windows 上使用 CMake 来确定依赖关系?

GET_RUNTIME_DEPENDENCIES 这样称呼:

file(GET_RUNTIME_DEPENDENCIES
    RESOLVED_DEPENDENCIES_VAR RES
    UNRESOLVED_DEPENDENCIES_VAR UNRES
    CONFLICTING_DEPENDENCIES_PREFIX CONFLICTING_DEPENDENCIES
    EXECUTABLES ${EXECS}
    LIBRARIES ${LIBS} ${MODULES} ${QTPLUGINS_LIBS}
    DIRECTORIES ${RUNTIME_DEPENDENCIES_DIRECTORIES}
    POST_EXCLUDE_REGEXES ${PLATFORM_POST_EXCLUDE_REGEXES}
)

WhereLIBS包含 my dllbut no RESno UNREScontains path to 3rd paty dll

标签: dllcmakedllimport

解决方案


因此,在较新的 CMake 中,所有这些运行时依赖查找魔法都存在严重的问题,这根本不是他们的错。问题是你、我和大约 90% 的其他 CMake 用户世界一直在查找错误的模块#THISWHOLETIME 现在我们的鸡已经回家栖息,因为正如你可能发现的那样,GET_RUNTIME_DEPENDENCIES/将如果您尝试将它们与未正确设置它们的查找模块创建的具有(我现在知道的)损坏的依赖关系的目标一起使用,那么所有这些都完全被抛弃了。所以,上个月我在 CMake Discourse 论坛上发布了这篇文章(我之前的链接):RUNTIME_DEPENDENCY_SET$<TARGET_RUNTIME_DLLS>IMPORTED

Windows 库、查找模块和 TARGET_RUNTIME_DLLS

Windows DLL Question™ 以前以一种或另一种形式出现过无数次,但它被 以新$<TARGET_RUNTIME_DLLS>的视角呈现,所以这里有一个新的看法。

如果你和我一样(根据我在公共项目的源代码树中观察到的情况,大约 90% 的 CMake 用户/开发人员都和我一样),你在 Windows 上编写 Find 模块的方法可能是这样的:

  1. 在所有三个桌面平台上使用相同的代码
  2. 让 CMake 发现.lib/.dll.a导入库而不是实际的 DLL,使用find_library().
  3. 最终将您的目标创建为 UNKNOWN IMPORTED,因为如果您尝试仅使用导入库创建 SHARED IMPORTED 库目标,它将无法正常工作,但 UNKNOWN IMPORTED 工作得很好,所以嗯。
  4. 将导入库设置为目标IMPORTED_LOCATION,因为这似乎工作正常。
  5. 收工,因为嘿-一切都编译了。

这已经为我们所有人服务了多年(实际上是几十年),所以我们大多只是接受它作为 CMake 在 Windows 上的工作方式。

但现在来了$<TARGET_RUNTIME_DLLS>。如果您尝试在 Windows 上实际使用它,您可能已经发现,虽然您的所有 CONFIG 模式包依赖项的 DLL 都被很好地捕获,但生成器表达式将愉快地忽略从 Find 模块创建的任何目标就像我上面描述的那样。......这可能是他们中的大多数。(在我自己的库构建中,所有这些,甚至是我没有编写的。)

为了$<TARGET_RUNTIME_DLLS>工作,IMPORTED 目标必须正确定义为共享库目标,并且需要IMPORTED_正确设置其属性:import lib path in IMPORTED_IMPLIB,DLL path in IMPORTED_LOCATION

所以,现在我有了这个新模块,它使用DLLTOOL.EXE它及其方便-I的标志来获取导入库的 DLL 的名称,然后使用find_program(). (只是因为find_library()不匹配 DLL,我想查看 PATH。我可以使用find_file()但我很确定我必须明确地给它更多的搜索路径。)

宏接受一个参数,即您已配置变量的名称<prefix>_IMPLIB。(或者<prefix>_IMPLIBS,它与复数无关,并且在命名其输出变量时将遵循您的输入使用的任何形式。)

您传递给其名称的变量应该已经包含导入库的有效路径。通常这是由 设置的find_library(),即使我们都将它们视为运行时库 (DLL),但实际上它们并非如此。

有了find_library(<prefix>_IMPLIB ...)输出,implib_to_dll(<prefix>_IMPLIB)将尝试发现并<prefix>_LIBRARY使用导入库的关联运行时 DLL 的路径自动填充相应的变量。

将所有正确的变量设置为正确的值后,现在可以在 Windows 上正确配置 SHARED IMPORTED 库目标。$<TARGET_RUNTIME_DLLS>然后可用于发现和操作由这些目标定义的 DLL 集。

Find 有点痛苦,而且确实有点像 CMake 至少可以半自动地做的事情。但是,至少现在它有效。

现在我只需要重写我所有的 find 模块来使用它。叹。

ImplibUtils.cmake

#[=======================================================================[.rst:
IMPLIB_UTILS
------------

Tools for CMake on WIN32 to associate IMPORTED_IMPLIB paths (as discovered
by the :command:`find_library` command) with their IMPORTED_LOCATION DLLs.

Writing Find modules that create ``SHARED IMPORTED`` targets with the
correct ``IMPORTED_IMPLIB`` and ``IMPORTED_LOCATION`` properties is a
requirement for ``$<TARGET_RUNTIME_DLLS>`` to work correctly. (Probably
``IMPORTED_RUNTIME_DEPENDENCIES`` as well.)

Macros Provided
^^^^^^^^^^^^^^^

Currently the only tool here is ``implib_to_dll``. It takes a single
argument, the __name__ (_not_ value!) of a prefixed ``<prefix>_IMPLIB``
variable (containing the path to a ``.lib`` or ``.dll.a`` import library).

``implib_to_dll`` will attempt to locate the corresponding ``.dll`` file
for that import library, and set the variable ``<prefix>_LIBRARY``
to its location.

``implib_to_dll`` relies on the ``dlltool.exe`` utility. The path can
be set by defining ``DLLTOOL_EXECUTABLE`` in the cache prior to
including this module, if it is not set implib_utils will attempt to locate
``dlltool.exe`` using ``find_program()``.

Revision history
^^^^^^^^^^^^^^^^
2021-11-18 - Updated docs to remove CACHE mentions, fixed formatting
2021-10-14 - Initial version

Author: FeRD (Frank Dana) <ferdnyc@gmail.com>
License: CC0-1.0 (Creative Commons Universal Public Domain Dedication)
#]=======================================================================]
include_guard(DIRECTORY)

if (NOT WIN32)
  # Nothing to do here!
  return()
endif()

if (NOT DEFINED DLLTOOL_EXECUTABLE)
  find_program(DLLTOOL_EXECUTABLE
    NAMES dlltool dlltool.exe
    DOC "The path to the DLLTOOL utility"
  )
  if (DLLTOOL_EXECUTABLE STREQUAL "DLLTOOL_EXECUTABLE-NOTFOUND")
    message(WARNING "DLLTOOL not available, cannot continue")
    return()
  endif()
  message(DEBUG "Found dlltool at ${DLLTOOL_EXECUTABLE}")
endif()

#
### Macro: implib_to_dll
#
# (Win32 only)
# Uses dlltool.exe to find the name of the dll associated with the
# supplied import library.
macro(implib_to_dll _implib_var)
  set(_implib ${${_implib_var}})
  set(_library_var "${_implib_var}")
  # Automatically update the name, assuming it's in the correct format
  string(REGEX REPLACE
    [[_IMPLIBS$]] [[_LIBRARIES]]
    _library_var "${_library_var}")
  string(REGEX REPLACE
    [[_IMPLIB$]] [[_LIBRARY]]
    _library_var "${_library_var}")
  # We can't use the input variable name without blowing away the
  # previously-discovered contents, so that's a non-starter
  if ("${_implib_var}" STREQUAL "${_library_var}")
    message(ERROR "Name collision! You probably didn't pass "
    "implib_to_dll() a correctly-formatted variable name. "
    "Only <prefix>_IMPLIB or <prefix>_IMPLIBS is supported.")
    return()
  endif()

  if(EXISTS "${_implib}")
    message(DEBUG "Looking up dll name for import library ${_implib}")
    execute_process(COMMAND
      "${DLLTOOL_EXECUTABLE}" -I "${_implib}"
      OUTPUT_VARIABLE _dll_name
      OUTPUT_STRIP_TRAILING_WHITESPACE
    )
    message(DEBUG "DLLTOOL returned ${_dll_name}, finding...")

    # Check the directory where the import lib is found
    get_filename_component(_implib_dir ".." REALPATH
                           BASE_DIR "${_implib}")
    message(DEBUG "Checking import lib directory ${_implib_dir}")

    # Add a check in ../../bin/, relative to the import library
    get_filename_component(_bindir "../../bin" REALPATH
                           BASE_DIR "${_implib}")
    message(DEBUG "Also checking ${_bindir}")

    find_program(${_library_var}
      NAMES ${_dll_name}
      HINTS
        ${_bindir}
        ${_implib_dir}
      PATHS
        ENV PATH
    )
    set(${_library_var} "${${_library_var}}" PARENT_SCOPE)
    message(DEBUG "Set ${_library_var} to ${${_library_var}}")
  endif()
endmacro()

推荐阅读