我注意到 CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS CMake 变量有奇怪的不一致行为。考虑小型示例项目(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)
以及来源:
/****** 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();
}
配置后
cmake -G "MinGW Makefiles" ../
并尝试使用
进行编译cmake --build .
链接器失败并出现错误:
baz.cpp:5: undefined reference to `foo()'
我注意到:
FOOBAR_EXPORT
中删除 void bar
可以使其正确链接#include "foobar.h"
中删除 foobar.cpp
可以使其正确链接FOOBAR_EXPORT
添加到 void foo
可以使其正确链接我可以在
CMakeLists.txt
或 FOOBAR_EXPORT
的定义中进行哪些更改,以始终导出所有符号,即使其中一些符号在声明前面有明确的 __declspec
?
应用上述 3 个解决方案中的任何一个对我来说都是维护地狱,因为问题出在大型自动生成的 OpenAPI 代码中。
在 Windows 上链接时,除非满足某些条件,否则默认行为是“自动导出 DLL 中的所有符号”。首要条件是:
如果未在命令行上显式给出--export-all-symbols
,则如果满足以下任一条件,则默认自动导出行为将被禁用:
使用 DEF 文件。导出的整个符号集描述为:
- 任何目标文件中的任何符号都标有
__declspec(dllexport)
- 属性。
当自动导出运行时,ld 将导出它在 DLL 中找到的所有非本地(全局和公共)符号,除了一些已知属于系统运行时和库的符号。由于通常不需要导出 DLL 的所有符号,其中可能包括不属于任何公共接口的私有函数,因此上面列出的命令行选项可用于从导出列表中过滤符号。可以使用
--output-def
选项查看导出符号的最终列表,其中所有排除均已生效。
因此,如果您发现自己想要重新启用“所有符号导出”行为;您可以将
--export-all-symbols
选项添加到链接器。
实现这一点的命令行方法是-Wl,--export-all-symbols
。
实现此目的的 cmake 方法是将此选项添加到链接器的CMakeLists.txt
中;这可以通过将其添加到 CXX_FLAGS 来完成:
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wl,--export-all-symbols")
但是,cmake 表明在这种情况下,
CMAKE_SHARED_LINKER_FLAGS
是一个稍微更好的变量选择。