- 创建 DLL (动态链接库):编写代码并编译成
.dll文件。 - 创建导入库 (
.lib):为了让调用方能顺利链接,我们需要一个.lib文件。 - 调用 DLL (可执行程序):编写代码,加载 DLL 并调用其中的函数。
环境准备
我们将使用 Visual Studio 和它的 MSVC (Microsoft Visual C++) 编译器,因为它对 Windows 平台的 DLL 支持最简单、最标准,如果你使用 MinGW (GCC),语法略有不同,但核心概念一致。

- 安装 Visual Studio (Community 版本是免费的)。
- 在安装时,确保勾选了 “使用 C++ 的桌面开发” 工作负载。
第一部分:创建 DLL 项目
我们将创建一个名为 MyMathLib 的数学库,它包含两个函数:Add 和 Multiply。
创建项目
- 打开 Visual Studio。
- 选择 “创建新项目”。
- 搜索并选择 “动态链接库 (DLL)” 模板,然后点击 “下一步”。
- 项目命名为
MyMathLib,选择一个位置,然后点击 “创建”。
编写 DLL 代码
Visual Studio 会为你生成一个 dllmain.c 文件,我们可以直接修改它,或者创建一个新的 .c 文件,为了清晰,我们创建一个 math.c 文件。
math.c
#include <windows.h> // 包含 Windows API 头文件,用于导出函数
// 定义导出宏,这是告诉编译器哪些函数需要被导出到 DLL 中的关键。
// 当 DLL 编译时,__declspec(dllexport) 会使函数可见。
// 当调用方编译时,这个宏应该是空的,所以我们通常使用条件编译。
#ifdef MYMATHLIB_EXPORTS // 在项目设置中定义这个宏
#define API __declspec(dllexport)
#else
#define API __declspec(dllimport)
#endif
// 导出的加法函数
API int Add(int a, int b) {
return a + b;
}
// 导出的乘法函数
API int Multiply(int a, int b) {
return a * b;
}
math.h (头文件)

#ifndef MATH_H #define MATH_H // 同样,在头文件中也使用这个宏来确保正确的导入/导出声明 #ifdef MYMATHLIB_EXPORTS #define API __declspec(dllexport) #else #define API __declspec(dllimport) #endif // 声明导出的函数,这样调用方可以知道这些函数的存在 API int Add(int a, int b); API int Multiply(int a, int b); #endif // MATH_H
设置项目属性以导出函数
为了让 MYMATHLIB_EXPORTS 宏在编译 DLL 时被定义,我们需要在项目属性中进行设置:
- 在 “解决方案资源管理器” 中右键点击
MyMathLib项目。 - 选择 “属性”。
- 在 “C/C++” -> “预处理器” -> “预处理器定义” 中,添加
MYMATHLIB_EXPORTS;(注意分号)。 - 点击 “应用” 和 “确定”。
编译 DLL
按下 Ctrl + B (生成解决方案) 或点击菜单栏的 “生成” -> “生成解决方案”。
编译成功后,你会在项目的 x64/Debug (或 x64/Release) 目录下找到两个关键文件:
MyMathLib.dll:动态链接库文件,这是运行时需要的。MyMathLib.lib:导入库文件,这是链接时需要的。
第二部分:调用 DLL 项目
现在我们创建一个新的控制台应用程序来使用刚刚创建的 MyMathLib.dll。

创建项目
- 在 Visual Studio 中,“创建新项目”。
- 选择 “控制台应用” 模板,语言选择 C++。
- 项目命名为
MyMathApp,然后点击 “创建”。
准备工作
- 复制文件:将
MyMathLib.dll和MyMathLib.lib从MyMathLib的输出目录复制到MyMathApp的输出目录 (x64/Debug或x64/Release)。- 为什么?
MyMathApp.exe运行时需要MyMathLib.dll,链接器需要MyMathLib.lib来找到函数的地址。
- 为什么?
- 包含头文件:将
MyMathLib项目中的math.h复制到MyMathApp项目文件夹下,并在你的源文件中包含它。
编写调用代码
有两种主要的方式来调用 DLL 中的函数:
-
隐式链接 (Implicit Linking / 静态加载)
- 这是最简单、最常用的方式,它通过链接器在程序启动时自动加载 DLL。
- 你需要
.lib文件和.h文件。
-
显式链接 (Explicit Linking / 动态加载)
- 更灵活,也更复杂,你在程序运行时手动加载和卸载 DLL。
- 你只需要
.dll文件,不需要.lib文件。
隐式链接 (推荐用于开发)
这是最标准、最简单的方法,链接器会帮你处理大部分事情。
main.c
#include <stdio.h>
#include <windows.h> // 包含 Windows API
// 包含我们自己的头文件
// 注意:这里不需要定义 MYMATHLIB_EXPORTS,因为我们是在“导入”函数
#include "math.h"
int main() {
int result1 = Add(10, 20);
printf("隐式链接: 10 + 20 = %d\n", result1);
int result2 = Multiply(10, 20);
printf("隐式链接: 10 * 20 = %d\n", result2);
// 程序结束时,Windows 会自动卸载 DLL
printf("程序执行完毕,\n");
return 0;
}
配置链接器:
为了让链接器知道去哪里找 MyMathLib.lib,你需要设置它的附加依赖项。
- 右键点击
MyMathApp项目 -> “属性”。 - 转到 “链接器” -> “输入” -> “附加依赖项”。
- 在输入框中,添加
MyMathLib.lib。 - 确保 “链接器” -> “常规” -> “附加库目录” 指向了
MyMathLib.lib所在的目录(通常是MyMathApp的输出目录)。 - 点击 “应用” 和 “确定”。
显式链接 (更灵活)
这种方法在运行时通过 API 加载 DLL,适合插件系统或可选功能。
main.c
#include <stdio.h>
#include <windows.h>
// 为了使用显式链接,我们不能直接使用 API 宏,需要自己处理函数指针
// 我们可以直接声明函数原型,但用 typedef 来定义函数指针类型,这样更安全
typedef int (*AddFunc)(int, int);
typedef int (*MultiplyFunc)(int, int);
int main() {
// 1. 加载 DLL
HMODULE hDll = LoadLibrary(L"MyMathLib.dll"); // 注意:使用宽字符 L"..."
if (hDll == NULL) {
printf("错误: 无法加载 MyMathLib.dll,请确保 DLL 文件在正确的路径下,\n");
return 1;
}
// 2. 从 DLL 中获取函数地址
AddFunc pAdd = (AddFunc)GetProcAddress(hDll, "Add");
MultiplyFunc pMultiply = (MultiplyFunc)GetProcAddress(hDll, "Multiply");
if (pAdd == NULL || pMultiply == NULL) {
printf("错误: 无法从 DLL 中获取函数地址,\n");
FreeLibrary(hDll); // 加载失败,记得释放
return 1;
}
// 3. 调用函数
int result1 = pAdd(10, 20);
printf("显式链接: 10 + 20 = %d\n", result1);
int result2 = pMultiply(10, 20);
printf("显式链接: 10 * 20 = %d\n", result2);
// 4. 卸载 DLL
FreeLibrary(hDll);
printf("程序执行完毕,\n");
return 0;
}
注意:使用显式链接时,你不需要在项目属性中设置 .lib 文件,也不需要包含那个特殊的 math.h,你只需要标准的 windows.h。
总结与对比
| 特性 | 隐式链接 | 显式链接 |
|---|---|---|
| 所需文件 | .dll, .lib, .h |
仅需 .dll |
| 加载时机 | 程序启动时,由操作系统加载 | 运行时,由 LoadLibrary 手动加载 |
| 链接方式 | 编译时链接器处理 | 运行时 GetProcAddress 获取函数地址 |
| 优点 | 简单,代码清晰,编译器检查类型 | 灵活,可以按需加载/卸载,适合插件 |
| 缺点 | 程序启动时必须能找到 DLL | 代码复杂,需要手动处理函数指针和错误 |
| 适用场景 | 标准库开发,依赖关系明确的程序 | 插件系统,可选功能,版本管理 |
对于初学者和大多数应用场景,隐式链接是首选,只有当你需要高度的灵活性时,才考虑使用显式链接。
编译并运行 MyMathApp 项目,你应该能看到成功调用 DLL 函数的输出。
