必须加__declspec(dllexport),否则函数不会出现在DLL导出表中,GetProcAddress将返回NULL;需配合extern "C"避免C++名字修饰,或用宏和CMake正确配置符号可见性。

用 Visual Studio 创建 DLL 项目时必须加 __declspec(dllexport)
不加这个声明,编译出来的 DLL 里根本找不到你的函数——链接器不会把普通函数放进导出表。哪怕代码能编译通过,调用方用 LoadLibrary 加载后,GetProcAddress 一定会返回 NULL。
常见写法是定义宏统一控制:
#define MYDLL_API __declspec(dllexport) extern "C" MYDLL_API int add(int a, int b);
注意两点:
-
extern "C"能避免 C++ 函数名修饰(name mangling),否则导出的是类似?add@@YAHHH@Z这样的符号,C 或其他语言几乎没法调用 - 如果只供 C++ 程序调用,可以不用
extern "C",但调用方必须用完全匹配的修饰名,非常容易出错 - Debug 和 Release 下导出行为一致,但建议在头文件中根据编译配置自动切换:
#ifdef MYDLL_EXPORTS…
cmake 构建 DLL 必须设 SHARED 并处理符号可见性
用 cmake 写 add_library(mydll SHARED ...) 是基础,但默认在 Windows 上仍不导出函数——CMake 不会自动加 __declspec。
立即学习“C++免费学习笔记(深入)”;
推荐做法:
- 源码中继续用
__declspec(dllexport)标记函数(最可控) - 或在 cmake 中加编译选项:
target_compile_definitions(mydll PRIVATE MYDLL_EXPORTS),配合头文件中的条件宏 - 避免用
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON):它会导出所有符号,包括 STL 内部符号、模板实例等,体积膨胀且可能引发冲突
导出类比导出函数更麻烦,慎用
直接导出整个 C++ 类(含虚函数、模板、STL 成员)极易出问题:
- 调用方和 DLL 的运行时(CRT)版本必须完全一致,否则
std::string构造/析构会崩溃 - 虚函数表布局依赖编译器和 ABI,跨工程/跨工具链基本不可靠
- 更好的做法是只导出纯 C 风格接口(
extern "C"函数),内部用 pimpl 模式封装类逻辑 - 如果真要导出类,至少把构造/析构也导出,并强制调用方用 DLL 提供的
create_XXX()/destroy_XXX(),而非直接new
检查 DLL 导出列表用 dumpbin /exports 最直接
别信 IDE 的“生成成功”,得亲眼看到符号在导出表里:
dumpbin /exports mydll.dll
输出中应出现类似:
1 0 00001000 ?add@@YAHHH@Z
2 1 00001010 _add@8
关键看两列:
- 右边是实际导出的符号名(带修饰的说明没加
extern "C") - 中间是序号(ordinal),为空表示按名导入;若填了数字,调用方可改用序号加载(更快,但不推荐,换版本易断)
- 如果列表为空或只有
_DllMain@12,说明导出声明漏了、宏没生效、或函数定义和声明不在同一编译单元
真正难的不是生成 DLL,而是让符号干净、稳定、可预测地出现在导出表里——多看一遍 dumpbin 输出,比反复重编译省时间得多。











