Why is manual copying of .dll needed when linking library?

I am trying to figure out how CMake is supposed to work for shared libraries. I create a shared library like so:

(in /mdp_opt/CMakeLists.txt:)

add_library (mdp_opt SHARED librarycomponent.cpp)

Now, a test executable uses this:
(/test/CMakeLists.txt:)
add_executable (test test.cpp)

target_link_libraries(test PRIVATE mdp_opt)

This works fine if the library is marked STATIC (instead of SHARED as above). When it is marked SHARED (as in above), I need to add:

set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)

I halfway understand why, but let’s forget that. What is more important is that with this change, it only works if I copy the file mdp_opt.dll from build/x64-debug/mdp_opt to build/x64-debug/test. I can do that - it is not a problem - but I don’t understand why?

In the book “professional CMake”, I read (emphasis mine):

LINK_LIBRARIES
This target property holds a list of all libraries the target should link to directly. It is initially
empty when the target is created and it supports generator expressions. An associated interface
property INTERFACE_LINK_LIBRARIES is supported. Each library listed can be one of the following:
• A path to a library, usually specified as an absolute path.
• Just the library name without a path, usually also without any platform-specific file name
prefix (e.g. lib) or suffix (e.g. .a, .so, .dll).
• The name of a CMake library target. CMake will convert this to a path to the built library
when generating the linker command, including supplying any prefix or suffix to the file
name as appropriate for the platform. Because CMake handles all the various platform
differences and paths on the project’s behalf, using a CMake target name is generally the
preferred method.

So didn’t I just state with:

target_link_libraries(test PRIVATE mdp_opt)

that I intend to link the output associated with the target mdp_opt with the test executable? Also, in my reading of the above resource, my understanding is that the executable will convert to a path? Anyhow?

So if the answer is that this somehow does not work and that some manual copying is needed (e.g. post-build), what is the preferred way to deal with this in a cross-platform way?

# ---------- Setup output Directories -------------------------
if(NOT DEFINED CMAKE_LIBRARY_OUTPUT_DIRECTORY)
  set(CMAKE_LIBRARY_OUTPUT_DIRECTORY
    ${${PROJECT_NAME} _BINARY_DIR}/Bin
    CACHE PATH
    "Single Directory for all Libraries"
    )
endif()

# --------- Setup the Executable output Directory -------------
if(NOT DEFINED CMAKE_RUNTIME_OUTPUT_DIRECTORY)
  set(CMAKE_RUNTIME_OUTPUT_DIRECTORY
    ${${PROJECT_NAME} _BINARY_DIR}/Bin
    CACHE PATH
    "Single Directory for all Executables."
    )
endif()

# --------- Setup the Executable output Directory -------------
if(NOT DEFINED CMAKE_ARCHIVE_OUTPUT_DIRECTORY)
  set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY
    ${${PROJECT_NAME} _BINARY_DIR}/Bin
    CACHE PATH
    "Single Directory for all static libraries."
    )
endif()

This will make sure all libraries and executables are placed into the same directory. This is one way to solve the issue.

This is essentially because Windows lacks an “rpath” mechanism where a library can contain a set of directories for the runtime loader to look at. On Windows, there is just PATH and various special flags one can pass to LoadLibrary (which is outside of your control).

3 Likes

Thanks, that is very helpful; I think I understand now why all this fuss is needed. It would help if documentation (like the quoted book) would mention these limitations on windows; I found the statements a bit misleading.

Sorry it has taken me so long to reply here, but are you talking about building or running your executable here? Building it shouldn’t require you to force the DLL for mdp_opt to be in the same directory as the test executable. When running the executable, that’s a different story. Either the DLL has to be in the same directory as the executable, or it has to be found in one of the directories specified by the PATH environment variable. How you run things is something CMake can’t control, so it’s up to you to get that right. Many projects dump all DLLs and executables in the same output directory so that their users don’t have to deal with it. That may be feasible for smaller projects, but for large projects with many executables (including test executables), that may not be appropriate.

I should also mention that you should not call any targets you define “test”. That target name is reserved for CMake’s own use. The enable_testing() command will cause a global test target to be defined, which would then clash with the one you’ve tried to define in the add_executable() call.

Thanks, that makes things clear; e.g. the distinction between building and running.

Still, in your book, a newbie to cmake like me might feel confused when they link a library with target_link_libraries, and then find that the executable won’t run because it cannot find the dll. A remark somewhere would help the learning curve.

Note that the reason for the split is for cross compilation where the paths on the build machine don’t necessary match what the deployment will see. DESTDIR-mediated installs is another complication here. For example, a “sysroot” might live on the build machine at /usr/sysroots/aarch64-linux-gnu while it is /usr on the deployment machine. CMake doesn’t have a model for deployment layouts to warn when things don’t match (in general or for the common case of “like the build machine”).