Visual Studio中C++ DLL创建与调用全攻略
在Windows平台开发中,动态链接库(DLL)是实现代码复用、模块化设计的关键技术。通过本指南,你将学习如何利用Visual Studio创建C++ DLL,掌握静态链接与动态加载的调用差异,理解函数导出命名规则,并正确处理DLL生命周期管理。
DLL 核心概念与应用场景
DLL(Dynamic Link Library,动态链接库)是将代码和数据封装在独立文件中的库,允许多个应用程序同时调用其中的功能。相比传统的源代码级“白盒复用”,DLL提供了“黑盒复用”的优势:
- 资源节约:多个程序共享同一份DLL代码,减少内存和磁盘占用。
- 模块化解耦:隐藏实现细节,避免命名冲突,便于独立更新和维护。
- 部署简化:只需分发DLL文件,无需暴露源码。
第一步:在 Visual Studio 中创建 DLL 工程
创建DLL工程的步骤与常规控制台应用类似,需选择特定的项目模板。
- 新建项目:打开 Visual Studio,选择“创建新项目”。在模板列表中,定位到
C++类别,选择 “动态链接库 (DLL)” 或类似名称的空项目模板。

- 配置工程:输入项目名称并点击“创建”。确保解决方案和工程设置符合默认要求,点击“完成”即可生成初始工程结构。

添加头文件与源文件
右键点击工程 > “添加” > “新建项”,创建头文件 MyCode.h 和源文件 MyCode.cpp。
定义导出接口 (MyCode.h)
为了实现条件编译(区分是作为库被编译,还是作为客户端使用),通常定义宏来控制导出/导入属性:
#ifndef _MYCODE_H_
#define _MYCODE_H_
// 若定义了导出宏,则标记为导出;否则标记为导入
#ifdef DLLDEMO1_EXPORTS
#define EXPORTS_DEMO __declspec(dllexport)
#else
#define EXPORTS_DEMO __declspec(dllimport)
#endif
// 使用 extern "C" 防止 C++ 名称修饰
extern "C" EXPORTS_DEMO int Add(int a, int b);
#endif
实现函数逻辑 (MyCode.cpp)
包含预编译头文件和自定义头文件,并实现具体逻辑:
#include "stdafx.h"
#include "MyCode.h"
int Add(int a, int b) {
return (a + b);
}
按 Ctrl+Shift+B 生成解决方案,将在输出目录(如 Debug 或 x64\Debug)下得到 DLLDemo1.dll 及对应的导入库 DLLDemo1.lib。
第二步:调用 DLL 的两种模式
C++ 客户端调用 DLL 主要有两种策略,分别适用于不同的业务场景。
1. 加载时动态链接 (Load-time Dynamic Linking)
特点:调用方式与普通本地函数无异,编译器自动处理链接过程。
适用:对启动性能敏感、且依赖关系固定的场景。
前提:需要同时拥有 DLL 的头文件(.h)和导入库文件(.lib)。
在客户端工程中引入库文件路径并声明函数:
#include <windows.h>
#include <iostream>
using namespace std;
// 链接器自动查找 DLLDemo1.lib
#pragma comment(lib, "..\\debug\\DLLDemo1.lib")
// 声明外部函数,匹配 DLL 中的导出签名
extern "C" __declspec(dllimport) int Add(int a, int b);
int main() {
cout << Add(2, 3) << endl;
return 0;
}
2. 运行时动态链接 (Run-time Dynamic Linking)
特点:在程序运行期间显式加载 DLL,灵活控制模块加载时机,无需 .lib 文件。
适用:需要根据用户选择或系统状态动态加载不同插件的场景,或希望加速主程序启动的情况。
核心 API:LoadLibrary(加载)、GetProcAddress(获取地址)、FreeLibrary(释放)。
#include <windows.h>
#include <iostream>
using namespace std;
// 定义函数指针类型以匹配 Add 签名
typedef int (*AddFunc)(int a, int b);
int main() {
// 1. 加载 DLL
HMODULE hDll = LoadLibrary("DLLDemo1.dll");
if (hDll != NULL) {
// 2. 获取函数地址
AddFunc add = (AddFunc)GetProcAddress(hDll, "Add");
if (add != NULL) {
// 3. 调用函数
cout << add(2, 3) << endl;
}
// 4. 释放 DLL
FreeLibrary(hDll);
}
return 0;
}
关键机制:函数导出与名称修饰
C++ 支持函数重载,编译器会根据参数类型生成唯一的内部标识符(即“名称修饰”,Name Mangling)。这导致直接使用函数名字符串在 GetProcAddress 中可能找不到函数。以下是两种常见的导出方案。
方案 A:使用 __declspec(dllexport)
这是最常用的方式。如果在导出函数时添加了 extern "C",则告诉编译器按照 C 语言的规则进行链接(不进行名称修饰),此时导出函数名即为声明时的名字。
验证方法:使用 Dependency Walker 等工具查看 DLL 导出表。若使用 extern "C",导出列表中将直接显示 Add;若未使用,则可能显示类似 ?Add@@YAHHH@Z 的修饰后名称。


方案 B:使用 .def (Module-Definition File) 文件
.def 文件为链接器提供显式的导出指令,常用于导出序号或强制指定函数名。
创建一个 mydll.def 文件:
LIBRARY "DLLDemo2"
EXPORTS
Add @ 1 ; 导出 Add 函数,序号为 1
在客户端使用序号调用(更稳定,不受语言编译影响):
HMODULE hDll = LoadLibrary("DLLDemo2.dll");
if (hDll != NULL) {
// 使用 MAKEINTRESOURCE 传入序号
AddFunc add = (AddFunc)GetProcAddress(hDll, MAKEINTRESOURCE(1));
if (add) {
cout << add(2, 3) << endl;
}
FreeLibrary(hDll);
}
DLL 生命周期管理:DllMain
DllMain 是 DLL 的入口点,由系统自动调用,用于处理进程/线程附加或分离时的初始化与清理工作。
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH:
// DLL 首次加载到进程地址空间
break;
case DLL_THREAD_ATTACH:
// 新线程在该进程中创建
break;
case DLL_PROCESS_DETACH:
// DLL 从进程地址空间卸载
break;
case DLL_THREAD_DETACH:
// 线程终止
break;
}
return TRUE;
}
注意事项:
DllMain应尽量简单,避免在此处执行耗时操作或引发阻塞。- 若进程是通过
TerminateProcess强行终止的,系统将不会调用DLL_PROCESS_DETACH,可能导致资源泄漏或未保存的数据丢失。 - 不建议在
DllMain中调用可能触发其它 DLL 加载的操作(如LoadLibrary),以免引发死锁。
总结
掌握 C++ DLL 的开发与调用是 Windows 平台软件架构的基础。通过选择合适的导出方式(__declspec 或 .def),配合 extern "C" 消除名称歧义,并利用动态加载机制实现模块化,可以构建出高内聚、低耦合的应用程序。以上就是 Visual Studio 中 C++ DLL 创建与调用的详细内容,更多关于 C++ 进阶开发的资料请关注本站其它相关文章!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









