__declspec(dllexport) 必须在定义处(.cpp)使用且宏需有效展开;类导出需修饰整个类;内联函数无法导出;linux下需配合-fvisibility=hidden与visibility("default")显式导出。

为什么 __declspec(dllexport) 在 Windows 上不生效?
常见现象是:明明加了 __declspec(dllexport),但用 dumpbin /exports 看不到符号,或者链接时提示 LNK2019: unresolved external symbol。
根本原因不是语法写错,而是编译器没“看到”这个修饰——它必须出现在**定义处**(.cpp 中),而非仅在头文件声明里;且不能被宏意外屏蔽(比如 #define API_EXPORTS 没定义,导致 API_EXPORT 展开为空)。
- 确保导出宏在实现文件(.cpp)中可见,例如:
#define MYLIB_EXPORTS放在 .cpp 顶部,或通过编译选项/DMYLIB_EXPORTS - 类成员函数若要导出,整个类需加
__declspec(dllexport),不能只修饰单个方法(除非显式实例化模板) - 内联函数(
inline)默认不生成外部符号,即使加了__declspec(dllexport)也无效
Linux 下 __attribute__((visibility("hidden"))) 怎么让符号真正隐藏?
很多人以为加了 visibility("hidden") 就万事大吉,结果 nm -D 仍能看到一堆符号——这是因为默认 visibility 是 default,而该属性只对**新声明**起作用,不会 retroactively 影响已声明的符号。
关键点在于编译开关和头文件配合:
立即学习“C++免费学习笔记(深入)”;
- 必须启用
-fvisibility=hidden编译选项,否则属性无效 - 导出的接口要在头文件中显式标记
__attribute__((visibility("default"))),否则即使定义了也会被隐藏 - 模板实例化、静态局部变量、内联函数体内的符号不受 visibility 控制,可能意外泄露
-
extern "C"块内的声明不继承 visibility 属性,需单独标注
跨平台符号控制:头文件里怎么写一个通用导出宏?
Windows 和 Linux 对符号可见性的控制机制完全不同,硬写两套逻辑容易漏掉条件分支。最稳妥的做法是统一用宏封装,并把平台判断交给预处理器。
示例(推荐直接复制使用):
#ifdef _WIN32
#ifdef MYLIB_BUILDING_DLL
#define MYLIB_API __declspec(dllexport)
#else
#define MYLIB_API __declspec(dllimport)
#endif
#else
#ifdef __GNUC__
#define MYLIB_API __attribute__((visibility("default")))
#else
#define MYLIB_API
#endif
#endif
注意:MYLIB_BUILDING_DLL 必须只在构建动态库时定义(如 CMake 中 target_compile_definitions(mylib PRIVATE MYLIB_BUILDING_DLL)),否则使用者会误连 dllimport 版本。
- 不要在头文件里用
#ifdef __linux__直接写__attribute__,GCC 以外的编译器(如 Clang)也支持,但写死平台名反而限制兼容性 - 宏名末尾带
_API是约定俗成,避免和用户变量名冲突 - C++ 类导出时,
MYLIB_API要放在class关键字前,不是类名后
隐藏符号后,调试时找不到函数怎么办?
符号隐藏成功后,gdb 或 Visual Studio 调试器 可能无法设置断点或显示调用栈——这不是 bug,是预期行为:调试器依赖符号表,而 visibility("hidden") 或未导出的 dllexport 会让符号从动态符号表(.dynsym)中消失。
解决思路不是取消隐藏,而是分层处理:
- 发布版(Release)启用完全隐藏;调试版(Debug)可临时关闭
-fvisibility=hidden或保留部分符号用于诊断 - Windows 下可用
/DEBUG:FULL生成完整 PDB,即使符号不导出,调试器仍能通过 PDB 定位源码(但不能从外部 DLL 直接调用) - Linux 下可保留调试符号(
-g)并用objcopy --strip-unneeded移除动态符号,不影响调试体验
真正容易被忽略的是:C++ 模板函数即使没显式导出,在实例化点仍会生成本地符号,可能被误认为“泄露”,其实它们根本进不了动态符号表——查证要用 nm -C -D(只看动态导出)而非 nm -C(看所有符号)。










