CMake C++ Custom Library on Windows “Undefined Reference” – No Error on Linux

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.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.