Adding a library to a qt6 cmake project

Hello guys,

i am just trying to use a library for a robot in my qt6 project.

in my cmake file i have added the library.h file to the project sources and i have added the library.lib file like follows:

target_link_libraries(RadiationMaster PRIVATE C:/git/RadiationMaster/DobotM1Dll/DobotDllM1.lib)

there are also .dll files, i have added those to the Windows/System32 folder so they can be found.

But i still get undefined reference errors when trying to compile my program.

This is the first time i try to use dlls and have no idea how to properly add libs like that to a project.

Some help or even a link to a good tutorial on the topic would be very helpful!

Thanks a lot Michael

Yeah, you probably don’t want to do that. What you should do depends a bit on the nature of the dependency.

If the DLL is from somewhere completely external to your project (meaning, it’s supplied precompiled and you aren’t building it), you’ll probably want to define an IMPORTED TARGET for it. You might do this by way of a Find module for compatibility, especially if you plan to make your project available for others to build. (That hardcoded disk path isn’t going to work out so well if the build system is generated on a different machine.)

But even if you don’t use a Find module, you should still set your dependency DLL up as its own IMPORTED target. Specifically, you’d want a SHARED IMPORTED target, which should have both the .dll and .lib file paths set as properties. (You really shouldn’t ever install ANYTHING into C:\Windows\System32\, that’s major hackery and a recipe for trouble. You’re better off placing the DLLs you need in the same directory as the executable, Windows will always load them from there.)

As far as your CMake target goes, though, the brute-force hardcoded version would look something like this:

add_library(mylib SHARED IMPORTED)
set_property(TARGET mylib PROPERTY
  IMPORTED_LOCATION "full_path_to_dll_file")
set_property(TARGET mylib PROPERTY
  IMPORTED_IMPLIB "full_path_to_lib_file")
target_include_directories(mylib INTERFACE
  "full_path_to_any_header_files_needed")
target_compile_definitions(mylib INTERFACE
  [any defines to apply when the library is used])
# etc...

# Then, assuming you've created an executable build target:
add_executable(myexe ${SOURCES})
# you can link the DLL into your project's executable with:
target_link_libraries(myexe PRIVATE mylib)

But like I said, for portability you should really wrap that logic in a Find module that you can bundle with your project. That link has some information, but one of the best resources is reading the set of Find modules that come with CMake, they should be in a Modules directory somewhere in your CMake install dir. Any file named FindSomething.cmake on the CMAKE_MODULE_PATH is interpreted as a Find module for the dependency “Something”, and can be invoked from your CMakeLists.txt using a find_package(Something) call.

The biggest advantage to setting up proper IMPORTED targets, when building on Windows, is that you can then use features like the $<TARGET_RUNTIME_DLLS> generator expression to copy all of the necessary DLLs into the install directory when installing your binary. (Instead of putting them in C:\Windows\System32; seriously just say no.)

Oh, right: If, OTOH, the DLL is something you’re building from source, rather than building it first and then incorporating it into your project already-built, one option is to import the source repo for the DLL as an ExternalProject and build it with your own project. You can set the build up so that CMake will download the code directly from its source repo, compile the DLL, and incorporate it into your project as a library target constructed from the build directory, all as a prerequisite to compiling your code.

For projects that other people might have an interest in building for themselves, that’s a handy way to incorporate dependencies that aren’t already separately-installable parts of the CMake ecosystem.

Thanks a lot for the reply, i was not aware that windows will always load the DLLs if they are in the same folder than the executeable, thats for sure a better way than using the System32 folder!

When it comes to absolute paths you are correct, that it is not a good idea, i was just trying to get it working somehow and did not know better how to get my lib working.

I will try to get the library working with your information and will update the thread accordingly.

Hi Michael,

You should import the *.lib files using the target_link_libraries to your RadiationMaster. The DLLs need to be copied to the same folder the RadiationMaster is. You could use an add_custom_command for that.

Something like

add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
    COMMAND ${CMAKE_COMMAND} -E copy_if_different
    "PathOfDll/radiation.dll"
    $<TARGET_FILE_DIR:${PROJECT_NAME}>)

:+1: That’ll work, if you want to be able to run your executable from the build directory. Though, if you’ve set up an IMPORTED target for the dependenc(y/ies), you can easily copy the DLLs for any of its dependencies in one fell swoop using TARGET_RUNTIME_DLLS:

add_custom_command(TARGET myexe POST_BUILD
  COMMAND ${CMAKE_COMMAND} -E copy_if_different
  $<TARGET_RUNTIME_DLLS:myexe>
  $<TARGET_FILE_DIR:myexe>
  COMMAND_EXPAND_LISTS)

If you also want to be able to install it with the relevant DLLs, you can make use of CMake 3.21+'s IMPORTED_RUNTIME_ARTIFACTS support, alongside installing the built targets themselves:

install(TARGETS myexe
  RUNTIME DESTINATION bin)
install(IMPORTED_RUNTIME_ARTIFACTS mylib
  RUNTIME DESTINATION bin)

That’ll install the myexe target’s executable output file, and the .dll file associated with the mylib target, together into the directory $<INSTALL_PREFIX>/bin/. (As with $<TARGET_RUNTIME_DLLS>, this will only work if mylib is a SHARED IMPORTED target with the full path to the DLL set in its IMPORTED_LOCATION property.)

thanks again for the answers, a short update on the topic:
i had quite some issues with the dll and down the line dependencies. I solved these by porting everything back to Qt5/32Bit, for some reason i could not get the 64Bit version of the lib to work with qt6…

i will definitly tr out the add_custom_command to get the DLLs into the right folder

for some reason that does only work up until two DLLs, if i try to copy more i get the following:

Error copying file (if different) from "multiple_given_paths" to "destination_folder".

@ferdnyc
the same does also happen when using $<TARGET_RUNTIME_DLLS:myexe>, it tries to copy multiple files but fails

Hmm, strange.

Did you make sure to include COMMAND_EXPAND_LISTS in the arguments to add_custom_command()?

If so, it’s possible copy_if_different doesn’t support multiple source files for some reason. You could try using just copy instead. That’s what the example I cheesed that command from was using.

Aah thanks again, i forgot the COMMAND_EXPAND_LISTS!
Now everything works just fine :slight_smile:

You could also run windeployqt, something like:

# Run windeployqt

# Retrieve the absolute path to qmake and then use that path to find
# the binaries
get_target_property(_qmake_executable Qt6::qmake IMPORTED_LOCATION)
get_filename_component(_qt_bin_dir "${_qmake_executable}" DIRECTORY)
find_program(WINDEPLOYQT_EXECUTABLE windeployqt HINTS "${_qt_bin_dir}")
find_program(MACDEPLOYQT_EXECUTABLE macdeployqt HINTS "${_qt_bin_dir}")

add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
        COMMAND "${CMAKE_COMMAND}" -E
        env PATH="${_qt_bin_dir}" "${WINDEPLOYQT_EXECUTABLE}"
        "$<TARGET_FILE:${PROJECT_NAME}>"
        COMMENT "Running windeployqt...")

That’s for copying the qt dlls, the ones your target needs.

Thanks, that should also work.
But i’m fine with how i have it now.
It was just the COMMAND_EXPAND_LISTS parameter that was missing, that’s why it did not work…