c++cmakemingwdllimportdllexport

CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS doesn't export symbols if one of the functions is explicitly prefixed by __declspec


I noticed weird inconsistent behavior of CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS CMake variable. Considering small example project (Windows, MinGW):

cmake_minimum_required(VERSION 3.20 FATAL_ERROR)

project(Test VERSION 1.0.0)

option(BUILD_SHARED_LIBS "Build the project using shared libraries (Windows *.dll, or Linux *.so)" ON)

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O3")
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
set(CMAKE_CXX_STANDARD 17)

add_library(foobar foobar.cpp)
target_compile_definitions(foobar PRIVATE "FOOBAR_LIBRARY")
add_library(baz baz.cpp)
target_link_libraries(baz PUBLIC foobar)

and the sources:

/****** foobar.h ******/
#pragma once

#if defined(FOOBAR_LIBRARY)
#define FOOBAR_EXPORT __declspec(dllexport)
#else
#define FOOBAR_EXPORT __declspec(dllimport)
#endif

void foo();
FOOBAR_EXPORT void bar();
// void bar();  // removing explicit FOOBAR_EXPORT makes it work

/****** foobar.cpp ******/
#include "foobar.h"  // commenting-out the header include will make it work
void foo() {}
void bar() {}

/****** baz.cpp ******/
#include "foobar.h"

void baz() {
    foo();
    bar();
}

After configuring it with

cmake -G "MinGW Makefiles" ../

and trying to compile with

cmake --build .

the linker fails with error:

baz.cpp:5: undefined reference to `foo()'

I noticed that:

What can I change in my CMakeLists.txt or definitions of FOOBAR_EXPORT to always have all symbols exported, even if some of them have explicit __declspec in front of declaration?

Applying any of the 3 solutions above is a maintenance hell for me, because the problem is in large autogenerated OpenAPI code.


Solution

  • When linking on windows, there is a default behavior to automatically export all symbols in a DLL unless some conditions are met. The primary condition is:

    If --export-all-symbols is not given explicitly on the command line, then the default auto-export behavior will be disabled if either of the following are true:

    • A DEF file is used.
    • Any symbol in any object file was marked with the __declspec(dllexport) attribute.

    The overall set of symbols exported is described as:

    When auto-export is in operation, ld will export all the non-local (global and common) symbols it finds in a DLL, with the exception of a few symbols known to belong to the system’s runtime and libraries. As it will often not be desirable to export all of a DLL’s symbols, which may include private functions that are not part of any public interface, the command-line options listed above may be used to filter symbols out from the list for exporting. The --output-def option can be used in order to see the final list of exported symbols with all exclusions taken into effect.

    So, if you find yourself in the sitation where you want to re-enable the 'all symbol export' behavior; you can add the --export-all-symbols option to the linker.

    The command line way to get that to happen is -Wl,--export-all-symbols.

    The cmake way to get this to happen is to add this option to the CMakeLists.txt for the linker; This can be accomplished by adding it to the CXX_FLAGS:

    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wl,--export-all-symbols")
    

    However, cmake indicates that CMAKE_SHARED_LINKER_FLAGS is a slightly better choice of variable in this case.