




正确配置生成DLL需确保三点:项目属性设为Dynamic Library;源文件用__declspec(dllexport)或.def文件导出符号;头文件用宏区分dllimport/dllexport并加extern "C"防名字修饰。
生成 DLL 的关键不是写代码,而是让编译器知道“这个工程要导出符号”。默认新建的 C++ 动态库项目(DLL)已设好基础配置,但手动建空项目或从静态库改过来时极易漏掉 __declspec(dllexport) 或项目属性设置。
必须确认以下三点:
Dynamic Library (.dll)
__declspec(dllexport)(Windows 特有),或更推荐的方式——通过模块定义文件(.def)统一导出__declspec(dllexport),建议配合宏封装,避免在调用方误用:例如定义 #define MYDLL_API __declspec(dllexport),并在头文件中对导出函数加该宏;调用方则用 __declspec(dllimport)
不加导出声明的函数,即使编译成 DLL,链接时也会报 LNK2019: unresolved external symbol。
头文件是调用方和实现方的契约。写错会导致编译通过但运行崩溃,或链接失败。
典型错误是直接在头文件

__declspec(dllexport) —— 这会让调用方也尝试导出,引发重复定义或链接错误。
正确做法是用宏区分编译角色:
#ifdef BUILDING_MYDLL
#define MYDLL_API __declspec(dllexport)
#else
#define MYDLL_API __declspec(dllimport)
#endif
extern "C" {
MYDLL_API int add(int a, int b);
}
其中 BUILDING_MYDLL 是你在 DLL 工程的“预处理器定义”里手动添加的宏(项目属性 → C/C++ → 预处理器 → 预处理器定义)。这样头文件在 DLL 编译时展开为 dllexport,在调用方编译时展开为 dllimport。
加 extern "C" 是为了禁用 C++ 名字修饰(name mangling),否则 C++ 函数导出后名字乱码,C# 或其他语言根本没法调用。
这是最常用、最稳妥的调用方式,但三者路径和内容必须严格匹配,否则运行时报 0xc000007b 或 找不到指定模块。
调用方工程需配置:
$(SolutionDir)MyDll\include).lib 文件所在目录(如 $(OutDir),即 DLL 输出目录)MyDll.lib(注意不是 MyDll.dll)MyDll.dll 放到调用方可执行文件同目录下(或系统 PATH 路径中)常见坑:
ERROR_BAD_EXE_FORMAT
.dll 到输出目录,调试时提示 LoadLibrary failed: 126
extern "C" 时)→ 运行时 GetProcAddress 返回 NULL这种方式绕过链接期绑定,适合运行时决定是否加载、或多版本共存等场景,但写法稍繁琐,且失去编译期类型检查。
核心是三步:
LoadLibrary(L"MyDll.dll") 获取模块句柄(返回 HMODULE)GetProcAddress(hModule, "add") 获取函数地址(注意:C++ 函数名需用修饰后名字,除非加了 extern "C";C 函数名就是字符串字面量)typedef int (*ADD_FUNC)(int, int); ADD_FUNC pAdd = (ADD_FUNC)GetProcAddress(...); int r = pAdd(3, 4);
必须检查每一步返回值:如果 LoadLibrary 返回 NULL,用 GetLastError() 查具体原因(比如路径错、依赖缺失、位数不匹配);如果 GetProcAddress 返回 NULL,大概率是函数名拼错或未导出。
别忘了最后调用 FreeLibrary(hModule),否则 DLL 句柄泄漏,多次加载后进程无法退出。
导出类比导出函数更复杂,涉及虚表、内存分配器一致性等问题,除非明确需要跨 DLL 边界传递对象,否则优先用纯 C 风格函数接口。另外,DLL 中尽量避免使用 STL 容器作为参数或返回值——不同模块可能用不同版本 CRT,导致析构崩溃。