Windows DLL导出需显式声明__declspec(dllexport),而Linux/macOS默认导出非static符号;跨平台应统一用MYLIB_API宏区分dllexport/dllimport与visibility("default")。

Windows 下 DLL 导出必须显式声明 __declspec(dllexport)
Linux/macOS 的 .so/.dylib 默认导出所有非 static 符号,而 Windows 的 DLL 默认不导出任何符号——哪怕函数是 public 或全局的。不加 __declspec(dllexport),链接器会报 LNK2019: unresolved external symbol,即使头文件里声明了函数。
常见错误是只在头文件里写声明,没用宏包裹导出属性:
#define MYLIB_EXPORT __declspec(dllexport) // 错误:没条件编译,Linux 编译直接报错 MYLIB_EXPORT void do_work();
正确做法是用宏区分编译环境与用途:
- 定义头文件时用
MYLIB_API宏,构建时根据是否在构建库本身来切换dllexport/dllimport - 构建动态库时定义
MYLIB_BUILDING_DLL,此时MYLIB_API展开为__declspec(dllexport) - 外部调用方不定义该宏,
MYLIB_API展开为__declspec(dllimport) - Linux/macOS 下该宏应为空(或仅用
__attribute__((visibility("default")))替代)
C++ 类成员函数导出需整个类加 __declspec(dllexport)
不能只对某个成员函数加 dllexport,否则链接器找不到 vtable、RTTI 信息,运行时报 access violation 或构造失败。Windows 要求类本身被标记,编译器才生成完整的导出符号。
典型错误示例:
class MYLIB_API MyClass { // ✅ 正确:类级别导出
public:
MyClass();
void run();
};
而下面这种写法会导致链接失败:
class MyClass {
public:
__declspec(dllexport) void run(); // ❌ 错误:vtable 不导出,调用方无法实例化
注意:__declspec(dllexport) 对模板类无效——模板实例化发生在使用端,DLL 内无法预生成符号。如需导出模板,必须显式实例化并导出(且调用方需用相同编译器版本)。
避免 C++ 名字修饰(name mangling)导致跨语言/跨编译器调用失败
Windows 上 MSVC 和 MinGW 生成的符号名完全不同(例如 void foo(int) 在 MSVC 是 ?foo@@YAXH@Z,MinGW 是 _Z3fooi),C#、Python ctypes 等根本无法识别。解决方式只有统一用 extern "C" 导出 C 风格符号。
实操要点:
- 导出函数必须声明在
extern "C"块中,且不能是类成员(C 没有成员函数概念) - 头文件中要防止重复包含导致 C++ 链接冲突:
#ifdef __cplusplus包裹extern "C" - 返回或参数含 C++ 类型(如
std::string、引用、重载函数)的接口,不能用extern "C"—— 必须拆成纯 C 接口(例如用const char*+ 长度 + 手动内存管理) - MinGW 用户还需注意:默认导出符号带下划线前缀(
_foo),可用extern "C" __attribute__((stdcall))或 DEF 文件控制
Linux/macOS 上启用 default 可见性并手动隐藏内部符号
Linux 默认导出全部符号,容易造成命名冲突(比如两个库都导出了 log_init),且增大二进制体积。GCC/Clang 支持 -fvisibility=hidden,配合 __attribute__((visibility("default"))) 显式导出,才是安全做法。
关键配置:
- 编译时加
-fvisibility=hidden(CMake 中设set(CMAKE_CXX_VISIBILITY_PRESET hidden)) - 所有需要对外暴露的函数/类,必须显式加
__attribute__((visibility("default"))) - 头文件中建议统一用宏封装:
#define MYLIB_API __attribute__((visibility("default"))) - 不加该属性的符号,在
nm -D libxxx.so中不会出现,也不会被 dlsym 找到
忽略这点最直接的后果:你写的库在 Linux 上看似能加载,但调用时 dlsym 返回 nullptr,因为符号根本没导出。
跨平台项目里,MYLIB_API 宏最终会长这样:
#if defined _WIN32
#ifdef MYLIB_BUILDING_DLL
#define MYLIB_API __declspec(dllexport)
#else
#define MYLIB_API __declspec(dllimport)
#endif
#else
#define MYLIB_API __attribute__((visibility("default")))
#endif
真正麻烦的从来不是写几行导出声明,而是确保所有构建系统(MSVC、CMake、Makefile、Xcode)、所有目标平台、所有调用语言都对齐这一套符号可见性逻辑——漏掉一个平台的宏定义,就等于埋了个静默崩溃点。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











