一、前言
一个完整的 DLL 工程必须满足两个核心条件:
- 正确的项目类型配置 :必须生成
.dll文件,而不是.exe。 - 显式的符号导出:必须将需要公开的函数/类声明为导出,否则外部程序无法链接。
本文将从项目创建、代码编写、项目配置、生成产物、验证方法到常见错误,系统梳理 VS 生成 DLL 的完整流程。
二、完整生成流程
步骤 1:创建正确的 DLL 项目
方式 A:新建项目时直接选择 DLL 模板
- 打开 Visual Studio,选择 "创建新项目"。
- 搜索并选择 "动态链接库 (DLL)" 模板。
- 输入项目名称、位置,点击 "创建" 。

使用此模板时,VS 通常会自动设置好项目类型、预处理器宏等基础配置。
方式 B:将现有项目改为 DLL
如果已经创建的是"空项目"或"控制台应用",需要手动修改:
- 右键项目 → 属性。
- 进入 配置属性 → 常规 → 配置类型。
- 将值改为 "动态库 (.dll)"。
修改后需重新生成解决方案。
步骤 2:编写头文件并声明导出
头文件是 DLL 与调用方之间的"契约"。必须使用 __declspec(dllexport) / __declspec(dllimport) 机制来控制符号的导入/导出。
推荐宏定义方式
cpp
// MyDll.h
#pragma once
#ifdef BUILD_MYDLL
#define API_SYMBOL __declspec(dllexport)
#else
#define API_SYMBOL __declspec(dllimport)
#endif
extern "C" API_SYMBOL int add(int a, int b);
关键点说明
| 元素 | 作用 |
|---|---|
__declspec(dllexport) |
在 DLL 项目中使用时,表示该符号需要被导出到 DLL 中。 |
__declspec(dllimport) |
在调用方项目中使用时,表示该符号从外部 DLL 导入,可提升编译优化效率。 |
extern "C" |
关闭 C++ 名称修饰(Name Mangling),确保导出的函数名与声明名一致,便于 C 语言或其他语言调用。 |
BUILD_MYDLL 宏 |
在 DLL 项目内定义,用于切换导出/导入模式。 |
导出 C++ 类时的写法
cpp
// MyDll.h
#ifdef BUILD_MYDLL
#define API_SYMBOL __declspec(dllexport)
#else
#define API_SYMBOL __declspec(dllimport)
#endif
class API_SYMBOL Calculator {
public:
int Add(int a, int b);
int Subtract(int a, int b);
};
注意:导出整个类时,所有成员函数都会被导出。如果只想导出部分函数,可以单独对函数加
API_SYMBOL。
步骤 3:实现源文件
cpp
// MyDll.cpp
#include "pch.h"
#define BUILD_MYDLL
#include "MyDll.h"
int add(int a, int b) {
return a + b;
}
实现文件需要在包含头文件之前定义 BUILD_MYDLL,这样头文件中的 API_SYMBOL 才会展开为 __declspec(dllexport)。
步骤 4:确保定义了 BUILD_MYDLL 宏
这一步是最容易遗漏的。如果未定义该宏,头文件中的 API_SYMBOL 会展开为 __declspec(dllimport),导致 DLL 项目自身也不会导出任何符号。
配置方法(推荐在源文件中定义)
在每个实现 DLL 的 .cpp 文件顶部、包含头文件之前添加:
cpp
#define BUILD_MYDLL
#include "MyDll.h"
替代方法:在项目属性中定义
- 右键 DLL 项目 → 属性。
- 进入 配置属性 → C/C++ → 预处理器 → 预处理器定义。
- 添加
BUILD_MYDLL。 - 确保配置为 所有配置 / 所有平台 (或分别对 Debug/Release、x86/x64 配置)。

在源文件中定义的方式更直观,能明确看到导出宏的生效位置,也便于排查问题。
步骤 5:生成项目并检查产物

点击 生成 → 生成解决方案 (或按 Ctrl+Shift+B)后,VS 会在输出目录(如 Debug/ 或 Release/)生成以下文件:
| 文件 | 说明 | 是否必需 |
|---|---|---|
MyDll.dll |
动态链接库本身,包含函数代码。 | 运行时必需 |
MyDll.lib |
导入库(Import Library),包含 DLL 导出符号的索引。 | 编译链接时必需 |
MyDll.exp |
导出文件,用于链接阶段。 | 通常由链接器自动生成 |
MyDll.pdb |
程序数据库文件,用于调试。 | 调试时必需 |
如果 DLL 没有任何导出的函数,VS 通常不会生成
.lib文件。这是一个重要的判断信号。
三、调用方(.exe 项目)如何使用 DLL
1. 引入头文件

已知文件名,仍需要添加搜索路径
调用方项目中包含 DLL 的头文件:
cpp
#include "MyDll.h"
此时由于调用方没有定义 BUILD_MYDLL,API_SYMBOL 会展开为 __declspec(dllimport)。
2. 链接导入库 .lib
在调用方项目中,需要链接 DLL 生成的 .lib 文件:
方式 A:通过项目属性链接


需要知晓库名和库路径
- 右键调用方项目 → 属性。
- 进入 配置属性 → 链接器 → 输入 → 附加依赖项。
- 添加
MyDll.lib。 - 进入 链接器 → 常规 → 附加库目录 ,添加
.lib文件所在目录。
方式 B:通过代码指令链接
cpp
#pragma comment(lib, "MyDll.lib")
需确保
.lib文件在链接器的搜索路径中。
3. 部署 .dll 文件
程序运行时,系统需要找到 .dll 文件。常见放置位置:
-
与调用方
.exe同一目录(最常用)。
-
系统 PATH 环境变量中的某个目录。
-
项目的工作目录。
不建议将自定义 DLL 放入
C:\Windows\System32,以免造成系统污染或权限问题。
四、验证 DLL 是否导出成功
方法 1:使用 dumpbin 工具
- 打开 "Developer Command Prompt for VS" 或 "x64 Native Tools Command Prompt"。
- 切换到 DLL 所在目录。
- 执行:
bash
dumpbin /exports MyDll.dll
- 查看输出中的 ordinal / hint / name 列表。
如果看到了你定义的函数名(如 add),说明导出成功。
方法 2:使用 Dependency Walker / Dependencies 工具
-
旧版工具:
depends.exe(Dependency Walker)。 -
新版推荐:Dependencies,支持 Windows 10/11。
打开 DLL 文件后,可以在导出表中查看所有导出的符号。
方法 3:检查是否生成了 .lib 文件
如果生成目录中没有 .lib 文件,几乎可以确定 DLL 没有成功导出任何符号。
五、常见问题与解决方案
问题 1:编译成功,但没有生成 .lib 文件
原因:DLL 项目没有成功导出任何符号。
排查方向:
-
检查是否在源文件中包含了
#define BUILD_MYDLL(或项目属性中定义了该宏)。 -
检查头文件中是否使用了
__declspec(dllexport)。 -
检查函数实现是否包含了正确的头文件。
问题 2:调用方报错:LNK2019 无法解析的外部符号
原因:调用方没有找到 DLL 的导入库或头文件。
解决方案:
-
确认
.lib文件路径已加入"附加库目录"。 -
确认
MyDll.lib已加入"附加依赖项"。 -
确认调用方包含的头文件与 DLL 项目一致。
问题 3:运行时报错:找不到 DLL / 无法定位程序输入点
原因 :运行时系统找不到 .dll 文件,或找到的旧版本 DLL 不包含该函数。
解决方案:
-
将
.dll复制到.exe同目录。 -
使用
dumpbin /imports MyExe.exe检查依赖的 DLL 名称。 -
检查 PATH 环境变量中是否存在同名旧 DLL。
问题 4:导出的函数名被 C++ 修饰,dumpbin 看到乱码
原因 :没有用 extern "C" 包裹函数声明。
解决方案 :对需要保持原名的函数使用 extern "C"。
问题 5:Debug 和 Release 配置行为不一致
原因:预处理器定义、附加库目录等配置只设置了单一配置。
解决方案 :在项目属性中,配置选择 "所有配置" ,平台选择 "所有平台",确保设置对 Debug/Release、x86/x64 均生效。
六、最佳实践与注意事项
-
命名规范
-
导出宏名建议与项目名关联,如
BUILD_MYPROJECT/API_SYMBOL。 -
避免使用过于通用的宏名,防止冲突。
-
-
头文件设计
-
头文件应同时服务于 DLL 自身和调用方。
-
不要在头文件中暴露内部实现细节。
-
-
ABI 兼容性
-
跨 DLL 边界传递对象时,注意 C++ 类的内存布局、编译器版本、运行时库一致性。
-
优先导出 C 风格接口,避免导出 STL 容器、模板类等 ABI 不稳定的类型。
-
-
运行时库匹配
-
DLL 项目和调用方项目应使用相同的运行时库配置(
/MD或/MDd)。 -
路径:项目属性 → C/C++ → 代码生成 → 运行库。
-
-
平台一致性
-
x86 的 DLL 不能被 x64 的 .exe 加载,反之亦然。
-
确保调用方和 DLL 的平台架构一致。
-
-
版本管理
- 为 DLL 添加版本信息资源(.rc 文件),便于后续识别和维护。
-
模块定义文件(.def)
-
除了
__declspec(dllexport),还可以使用.def文件导出符号。 -
适用场景:需要精确控制导出序号、隐藏真实函数名、导出未修饰名称等。
-
七、可选方案:使用模块定义文件(.def)导出
创建 MyDll.def
text
LIBRARY MyDll
EXPORTS
add
subtract
配置项目链接 .def 文件
- 右键项目 → 属性。
- 进入 链接器 → 输入 → 模块定义文件。
- 添加
MyDll.def。
使用
.def文件时,即使不在代码中写__declspec(dllexport),这些函数也会被导出。
八、总结
生成一个可用的 Visual Studio DLL,标准流程如下:
- 选择正确的项目类型 :新建"动态链接库 (DLL)"项目,或将现有项目配置类型改为
.dll。 - 声明导出符号 :头文件中使用宏 +
__declspec(dllexport)/__declspec(dllimport)。(条件编译导入还是导出) - extern "C" 禁止函数重载;默认使用预编译头,所以在每个源文件里需要
#include "pch.h" - 定义导出宏 :在 DLL 源文件中包含头文件前添加
#define BUILD_MYDLL(或在项目属性中定义)。 - 实现函数:源文件包含头文件并完成功能实现。
- 生成并检查产物 :确认生成
.dll和.lib文件。 - 验证导出 :使用
dumpbin /exports或 Dependencies 工具检查导出符号。 - 调用方配置 :链接
.lib,运行时部署.dll。
缺少项目类型配置或符号导出声明,都会导致"编译成功但 DLL 不可用"的问题。理解这两个核心点,是掌握 VS DLL 开发的关键。