dllcmakeenvironment-variablesmingwctest

CTest, CMake & MinGW: Executables build, but fail to run, because fresh DLL is not found


The top-level CMakeLists.txt contains:

include(CTest)
add_subdirectory(lib)
add_subdirectory(demo)
add_subdirectory(test)

lib/CMakeLists.txt is essentially:

add_library(MyLib <sources>)

demo/CMakeLists.txt is essentially:

add_executable(Demo demo.c)
target_link_libraries(Demo MyLib)

test/CMakeLists.txt is just:

add_test(NAME Demo COMMAND Demo)

From a gitlab-runner, we execute:

cmake -G "Ninja" -DCMAKE_INSTALL_PREFIX=C:\opt\x64 -B. ..
cmake --build
ctest --output-on-failure

The first two steps succeed; the third one fails with:

Start 1: Demo
1/1 Test #1: Demo .......................Exit code 0xc0000135
***Exception:   0.03 sec

If I retry:

cmake --install
ctest

then the test succeeds. So the sole problem is that build/lib/mylib.dll is not found when running ctest. Whereas C:\opt\x64\lib is in PATH, and therefore the DLL is found after cmake --install. Which, however, is not what we want: ctest shall always use the fresh DLL from the current build, not the installed version.

Under Linux, everything works correctly. Why doesn't it for Windows and MinGW? Is this a bug in CMake? How can we work around this so that ctest executes correctly on all platforms?


Solution

  • Your issue seems to be the Windows DLL search procedure failing to find mylib.dll when your Demo executable is run by ctest. The Windows DLL search order is specified here:

    1. The directory from which the application loaded.
    2. The system directory. Use the GetSystemDirectory function to get the path of this directory.
    3. The 16-bit system directory. There is no function that obtains the path of this directory, but it is searched.
    4. The Windows directory. Use the GetWindowsDirectory function to get the path of this directory.
    5. The current directory.
    6. The directories that are listed in the PATH environment variable. Note that this does not include the per-application path specified by the App Paths registry key. The App Paths key is not used when computing the DLL search path.

    So, you could modify your PATH environment variable to also include the location of the fresh DLL from the current build.

    A better, less error-prone solution might be to place the DLL in the same directory as the Demo executable. You can force CMake to use the same binary directory for both the DLL and executable by modifying your top-level CMake file:

    include(CTest)
    add_subdirectory(lib ${CMAKE_BINARY_DIR}/demo)
    add_subdirectory(demo ${CMAKE_BINARY_DIR}/demo)
    add_subdirectory(test)
    

    Alternatively, as a less localized approach, you can place the DLL in the same directory as the executable by setting CMAKE_RUNTIME_OUTPUT_DIRECTORY:

    set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
    

    Yet another alternative:

    add_test(NAME Demo COMMAND Demo WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/lib)