Here is the short version with a quick setup of the situation and the fix. After that, I’ll elaborate a bit.
TL;DR
Setup
I have a custom C++ library and a separate project for tests (all based on Qt 6). The test project requires the library for execution.
Here is a short excerpt of the CMake scripts, first the library, then the tests.
project(wt2-shared VERSION 2.0.0 DESCRIPTION "WorkTracker2 Shared
Library")
# To export symbols.
add_compile_definitions(WT2_LIBRARY)
# Snip header + source definitions
add_library(${PROJECT_NAME} ${SOURCES} ${HEADERS})
target_include_directories(${PROJECT_NAME} PUBLIC include/)
target_link_libraries(wt2-shared Qt6::Core Qt6::Sql)
project(wt2-shared-test VERSION 2.0.0 DESCRIPTION "WorkTracker2
Shared Library Tests")
# Snip header + source definitions
add_executable(${PROJECT_NAME} ${SOURCES} ${HEADERS})
target_include_directories(${PROJECT_NAME} PRIVATE ${INCLUDES})
target_link_libraries(${PROJECT_NAME} Qt6::Core Qt6::Test wt2-shared)
Error
This error only occurred on Windows, and it does not matter which toolchain I used, be it MinGW or MSVC. The result was always the same.
The following shows the MinGW error.
[100%] Linking CXX executable wt2-shared-test.exe
CMakeFiles\wt2-shared-test.dir/objects.a(testdatasource.cpp.obj):testdatasource.cpp:(.text+0x3e5):
undefined reference to `__imp__ZN4Data3Sql13SqlDataSourceC1E7QString'
CMakeFiles\wt2-shared-test.dir/objects.a(testdatasource.cpp.obj):testdatasource.cpp:(.text+0x401):
undefined reference to `__imp__ZN4Data3Sql13SqlDataSource4loadEv'
CMakeFiles\wt2-shared-test.dir/objects.a(testdatasource.cpp.obj):testdatasource.cpp:(.text+0x514):
undefined reference to `__imp__ZN4Data3Sql13SqlDataSourceC1E7QString'
CMakeFiles\wt2-shared-test.dir/objects.a(testdatasource.cpp.obj):testdatasource.cpp:(.text+0x530):
undefined reference to `__imp__ZN4Data3Sql13SqlDataSource4loadEv'
collect2.exe: error: ld returned 1 exit status
mingw32-make[2]: ***
[wt2-shared-test\CMakeFiles\wt2-shared-test.dir\build.make:142:
wt2-shared-test/wt2-shared-test.exe] Error 1
Solution
The add_library
definition in the CMakeLists.txt was incomplete.
To make it work, I added SHARED
because I want a shared library.
add_library(${PROJECT_NAME} SHARED ${SOURCES} ${HEADERS})
Continue reading, though, to get the full picture. There is more to it than just making the library a shared one.
Long Explanation
Story-time 😃
CMake Default Behavior
The long answer is a bit more complicated. Because I did not specify
SHARED
, CMake automatically created a static library. You can
influence this default behavior by setting the BUILD_SHARED_LIBS
variable, for example, as an option for your library users. If you
use your library yourself, then you do not need that, of course, at
which point setting or not setting SHARED
is the way to go.
How would I go about fixing the error without creating a shared library with this information in hand? And why does it work on Linux but not on Windows?
Why No Error On Linux?
Under the hood, I am using a macro to specify whether to import or
export symbols of my code, WT2_EXPORT
. It depends on a preprocessor
flag WT2_LIBRARY
that I only defined in the library. That means, in
the wt2-shared library project, the macro expands to Q_DECL_EXPORT
and in the test project to Q_DECL_IMPORT
. The Qt macros themselves
expand to the appropriate OS and compiler-specific implementation.
On Linux GCC, this resolves to the following:
#define Q_DECL_EXPORT __attribute__((visibility("default")))
#define Q_DECL_IMPORT __attribute__((visibility("default")))
On Windows, you get this:
#define Q_DECL_EXPORT __declspec(dllexport)
#define Q_DECL_IMPORT __declspec(dllimport)
Unfortunately, I have not found a definitive answer which I can link
to prove my theory. I suspect GCC on Linux ignores the __attribute
flag when you create a static library. The
documentation only talks about
shared objects in this context. Thus, on Linux, it worked "by
accident", no matter whether I specified SHARED
or STATIC
(or
nothing, defaulting to STATIC
) in the CMakeLists.txt of the library.
Use a Static Library
If your goal is to create a static library, then any form of
export/import statement is not required. In my case, I want a shared
library, which is why the TL;DR approach is a solution for me.
Otherwise, I could delete the WT2_EXPORTS
macro, or, if I want to
support both static and shared, create a more complex setup that
results in WT2_EXPORTS
to resolve to nothing for a static library.
That may be something to investigate when I finished the project, or
I want to tinker rather than code.
Links
The project in question can be found on Github.