使用CMake构建可执行文件和共享库,但运行时链接器找不到动态链接库。

14

我正在使用gcc(cygwin),gnu make,Windows 7和cmake。

我的cmake测试项目具有以下结构。

rootdir
|-- App
|   |-- app.cpp
|   +-- CMakeLists.txt
|-- Lib
|   |-- lib.cpp
|   |-- CMakeLists.txt
|-- MakeFileProject
+ CMakeLists.txt

根目录/App/app.cpp:

#include<string>
void printThemMessageToScreen(std::string input);//prototype
int main(int argc,char **argv){
 printThemMessageToScreen("this will be displayed by our lib");
 return 0;
}

根目录/Lib/lib.cpp:

#include<iostream>
#include<string>

void printThemMessageToScreen(std::string input){
 std::cout<<input;
}

根目录/CMakeLists.txt:

cmake_minimum_required(VERSION 2.6)
project(TestProject)

add_subdirectory(App)
add_subdirectory(Lib)

根目录/Lib/CMakeLists.txt:

add_library(Lib SHARED lib.cpp)

根目录/App/CMakeLists.txt:

# Make sure the compiler can find include files from our Lib library. 
include_directories (${LIB_SOURCE_DIR}/Lib) 

# Make sure the linker can find the Lib library once it is built. 
link_directories (${LIB_BINARY_DIR}/Lib) 

# Add executable called "TestProjectExecutable" that is built from the source files 
add_executable (TestProjectExecutable app.cpp) 

# Link the executable to the lib library. 
target_link_libraries (TestProjectExecutable Lib) 

现在,当我运行 cmake 和 make 时,所有东西都会生成并构建,没有错误,但是当我尝试执行可执行文件时,它会失败,因为生成的库找不到。

但是:如果我将 lib dll 复制到与应用程序 exe 相同的目录中,它就会被执行!

另外:如果我将库配置为静态,则也可以执行。

如何告诉运行时链接器在哪里查找我的 dll?

更新:

根据用户 Vorren 提出的方法解决方案:

我打开了注册表编辑器,并导航到以下键:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths

我创建了一个名为我的应用程序的新键:

在这种情况下:TestProjectExecutable.exe

之后,(默认)值被设置为包括文件名和扩展名的 TestProjectExecutable.exe 的完整路径。然后,我创建了另一个字符串值称为“Path”,并将值设置为 dll 所在的文件夹:

enter image description here


1
那么你很可能需要将包含dll的文件夹添加到你的PATH变量中。这并不是cmake或cygwin特定的问题,而是一个Windows问题。请参阅此处以了解Windows如何查找dll:http://msdn.microsoft.com/en-us/library/7d83bc18.aspx - drescherjm
@drescherjm 是的,将dll目录添加到PATH变量中就可以解决问题,但我想知道这是否是解决这个问题的“正确”方式… - Daniel Jawna
1
最简单的方法是将dll文件放在exe文件所在的同一文件夹中。我给你提供的链接展示了5种可能的方法来实现这一点,而且所有这些方法都被微软认为是可以接受的。尽管使用UAC会涉及到系统文件夹,需要提升权限和明确的权限。 - drescherjm
1
顺便提一下,使用cmake可以轻松设置输出文件夹,使.dll放在与exe相同的文件夹中:https://dev59.com/Smw15IYBdhLWcg3wd7lj 我在所有基于cmake的项目中都使用这种方法。 - drescherjm
我有同样的问题。我的exe文件在"../out/bin"下,我的dll文件在"../out/bin/plugins"下。在生产代码中,dll被显式地链接,因此它不在exe文件夹中也没有问题。但现在我想在"../out/bin"中创建一个隐式链接到dll的测试exe。我尝试使用此处找到的命令 "http://www.cmake.org/Wiki/CMake/Tutorials/Exporting_and_Importing_Targets" 导入库,但是exe仍然找不到dll。 - Knitschi
显示剩余3条评论
4个回答

12

您的问题并不在于链接器或编译器,而是关于Windows搜索DLL的方式。

操作系统将使用以下算法来定位所需的DLL:

查找:

  1. 应用程序特定路径注册表键中列出的目录;
  2. 当前进程的可执行模块所在的目录;
  3. 当前目录;
  4. Windows系统目录;
  5. Windows目录;
  6. PATH环境变量中列出的目录;

因此,如果您不想在操作系统目录中混杂您的应用程序专用DLL,则有两个合理的选择:

  1. 创建一个应用程序特定路径注册表项(我会选择这个选项)
  2. 将DLL放置在EXE相同的文件夹中;
  3. 修改PATH变量(但是如果您可以选择选项1,为什么要这样做呢?);

那么,当我想使用选项1时,可执行应用程序需要像Wix或Nullsoft安装程序一样的安装程序,以创建我的DLL的注册表条目,对吗? - Daniel Jawna
要么这样,要么你需要想办法从你的应用程序代码中修改注册表。请查看此链接: http://msdn.microsoft.com/en-us/library/windows/desktop/ms724871(v=vs.85).aspx - pdeschain
好的,我会研究一下并尝试你的第一个选项,在我今天的工作完成后立即开始。 - Daniel Jawna
是的,你的方法非常有效!我想详细说明一下我的问题,即如何使它与注册表应用程序路径配合使用。 - Daniel Jawna
这个答案有助于解决问题。我想我会尝试在执行测试exe时将我的dll文件夹暂时添加到PATH中。 - Knitschi
2
有没有办法让CMake编辑应用程序特定的路径注册表? - Chris

11

我比较喜欢的一个解决方法,是将共享库构建到与可执行文件相同的目录中。这通常是一个更简单的解决方案。

使用CMake实现这一点的一种方法是

set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)

或者你也可以根据构建版本设置输出目录。

参见如何让CMake将输出放在“bin”目录中?


我的答案中提到了第二个选项,并且在问题的评论中也有提到 ;) - pdeschain
问题发生在工作中。如果我想要更改dll的输出目录,我需要说服其他人更改dll的输出目录。我希望通过只更改我的本地CMakeLists文件中的某些内容来避免这种情况,以便于我的测试exe。 - Knitschi
2
对我来说,这也是最明智的选择。 - StarShine
@Knitschi 无需说服他们,因为您可以制作自己的 CMakeLists.txt(Visual Studio 解决方案),设置这个奇妙的变量并包含他们的 CMakeLists 文件。 - Liviu
@Liviu 我认为问题在于我们的项目有一个额外的文件夹用于一些插件dll,另一个文件夹用于可执行文件。生产代码手动加载插件,使用正确的路径。当我为插件代码编写测试时,可执行文件最终出现在exe文件夹中,我想避免在测试中手动加载插件dll。但是我现在在另一家公司工作,所以不用担心;-) - Knitschi

8

我发现一种(我认为)非常好的处理方法。它遵循将 .dll 添加到与 .exe 相同的目录的方法。您可以在 CMake 中进行如下操作:

if (WIN32)
# copy the .dll file to the same folder as the executable
add_custom_command(
    TARGET <app-target> POST_BUILD
    COMMAND ${CMAKE_COMMAND} -E copy_directory
    $<TARGET_FILE_DIR:<lib-target>>
    $<TARGET_FILE_DIR:<app-target>)
endif()

其中app-target是您正在构建的应用程序或库的名称(通过add_executableadd_library创建),而lib-target是使用find_package导入的库。

# full example
cmake_minimum_required(VERSION 3.14)

project(my-app-project VERSION 0.0.1 LANGUAGES CXX)

find_package(useful-library REQUIRED)

add_executable(my-application main.cpp)

target_link_libraries(my-application PUBLIC useful-library::useful-library)

if (WIN32)
# copy the .dll file to the same folder as the executable
add_custom_command(
    TARGET my-application POST_BUILD
    COMMAND ${CMAKE_COMMAND} -E copy_directory
    $<TARGET_FILE_DIR:useful-library::useful-library>
    $<TARGET_FILE_DIR:my-application>)
endif()

1
我尝试了已接受答案中的选项1(由pdeschain提供)。 我甚至创建了一个cmake钩子,自动注册链接库的路径。
function (xtarget_link_libraries target libs) # same as target_link_libraries but with additional improvements to allow windows find the library at runtime
LIST(REMOVE_AT ARGV 0)
SET(LIBS ${ARGV}) # this is to pass list into this function
target_link_libraries(${target} ${LIBS}) # call standard routine

if(WIN32)
    set(TFILE ".")
    get_property(slibs TARGET ${target} PROPERTY all_libs) # recall libs linked before
    set(LIBS ${slibs};${LIBS})
    set_property(TARGET ${target} PROPERTY all_libs ${LIBS}) # save all libs
    FOREACH(lib ${LIBS}) # compose a list of paths
        set(TFILE "${TFILE};$<TARGET_LINKER_FILE_DIR:${lib}>")
    ENDFOREACH()
    #add reg key
    add_custom_command(TARGET ${target} POST_BUILD COMMAND  reg add "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\${target}.exe" /v "Path" /d "${TFILE}" /f )
endif()
endfunction()

可以使用xtarget_link_libraries(test lib1 lib2)。应用程序将能够在其绝对路径中找到动态库。

但是,这里有一个大问题,即App Paths机制https://msdn.microsoft.com/en-us/library/windows/desktop/ee872121(v=vs.85).aspx#appPaths不允许为“Debug/test.exe”和“Release/test.exe”等设置不同的条目。所以对我来说,这是一个糟糕的选择。

您可以添加以下行以将Default键填充为建议帖子中的程序路径。

add_custom_command(TARGET ${target} POST_BUILD COMMAND reg add "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\${target}.exe" /ve /d "$<TARGET_FILE:${target}>" /f )

现在,您可以在系统中的任何位置运行test.exe...我想我的下一个尝试将是选项

  1. 使用cmake创建dll的符号链接。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接