diff --git a/yuwenhao/IAR/IAR.pdf b/yuwenhao/IAR/IAR.pdf deleted file mode 100644 index 53685fb..0000000 Binary files a/yuwenhao/IAR/IAR.pdf and /dev/null differ diff --git a/yuwenhao/Library/Library.md b/yuwenhao/Library/Library.md new file mode 100644 index 0000000..72f1d13 --- /dev/null +++ b/yuwenhao/Library/Library.md @@ -0,0 +1,1053 @@ +# Visual Studio 2019 库学习文档 + +## 1. 学习目标 + +本文档用于记录 Visual Studio 2019 平台下库文件的生成与使用方法,主要包括静态库和动态库两部分内容。 + +完成本文档学习后,应能够完成以下操作: + +1. 理解库文件的作用,以及库文件在工程复用、源码隐藏和多人协作中的意义; +2. 理解静态库和动态库的基本概念; +3. 能够在 VS2019 中创建并生成静态库 `.lib`; +4. 能够在其他工程中通过工程配置方式调用静态库; +5. 能够在代码中通过语句加载 `.lib`; +6. 能够在 VS2019 中创建并生成动态库 `.dll`; +7. 掌握通过导出语句生成动态库的方法; +8. 掌握通过模块定义文件 `.def` 生成动态库的方法; +9. 能够通过工程配置方式调用动态库; +10. 能够在代码中通过语句加载 `.lib` 调用动态库; +11. 能够在代码中通过 `LoadLibrary`、`GetProcAddress` 加载 `.dll` 并调用函数; +12. 能够排查库调用过程中常见的头文件、库路径、平台不一致、运行时找不到 DLL 等问题。 + +## 2. 文档目录结构 + +Library 学习文档按照基础学习任务要求进行存放,推荐目录结构如下: + +```text +Library +|---pictures +| |---Library_001_学习目标.png +| |---Library_002_目录结构.png +| |---Library_003_库的作用.png +| |---Library_004_静态库和动态库关系.png +| |---Library_005_创建静态库项目.png +| |---Library_006_静态库项目属性.png +| |---Library_007_生成静态库.png +| |---Library_008_调用静态库配置.png +| |---Library_009_创建动态库项目.png +| |---Library_010_导出语句生成动态库.png +| |---Library_011_DEF文件生成动态库.png +| |---Library_012_动态库调用配置.png +| |---Library_013_DLL运行目录.png +| |---Library_014_常见错误.png +| +---Library_015_验收总结.png ++---Library.md +``` + +图片统一放在 `pictures` 文件夹下,Markdown 文档中通过相对路径进行引用。 + +## 3. 库的基本概念 + +### 3.1 什么是库 + +库可以理解为已经写好并封装好的功能模块。库中通常包含一组函数、类或接口,其他工程可以通过包含头文件并链接库文件的方式来使用这些功能。 + +在 C/C++ 工程中,常见库文件包括: + +| 类型 | 常见后缀 | 说明 | +|---|---|---| +| 静态库 | `.lib` | 在链接阶段被合并到最终程序中 | +| 动态库 | `.dll` | 程序运行时加载和使用 | +| 动态库导入库 | `.lib` | 配合 `.dll` 使用,保存 DLL 中函数的导入信息 | +| 头文件 | `.h` | 提供函数声明、类型定义、宏定义等接口信息 | + +需要注意:VS2019 中 `.lib` 可能有两种含义。第一种是静态库文件,里面包含真正的函数实现;第二种是动态库的导入库,里面不保存完整实现,只保存链接 DLL 所需的导入信息。 + +### 3.2 使用库的意义 + +自建库或调用库的意义主要体现在代码复用、工程结构清晰、隐藏源码和团队协作等方面。 + +| 意义 | 说明 | +|---|---| +| 代码复用 | 一份稳定代码可以被多个工程重复使用 | +| 工程清晰 | 将算法、通信、驱动等模块拆分出来,主工程更清楚 | +| 隐藏源码 | 调用方只看到 `.h` 和 `.lib/.dll`,看不到内部实现源码 | +| 方便协作 | 不同人员负责不同库,通过接口进行协作 | +| 降低耦合 | 主程序只依赖接口,不直接关心库内部实现 | +| 提高编译效率 | 稳定模块可提前编译成库,减少主工程重复编译 | + +例如在嵌入式软件项目中,常见的库包括: + +```text +MotorControl.lib +Modbus.lib +CanProtocol.lib +FilterAlgo.lib +DriverLib.lib +``` + +主程序只需要调用库提供的接口,例如: + +```cpp +Motor_Start(); +Motor_SetSpeed(1000); +Modbus_Poll(); +``` + +而不需要关心库内部如何实现。 + +### 3.3 静态库和动态库的区别 + +静态库和动态库都可以实现代码复用,但它们参与程序的时机不同。 + +| 对比项 | 静态库 | 动态库 | +|---|---|---| +| 常见文件 | `.lib` | `.dll` + `.lib` | +| 使用时机 | 链接阶段合并到程序中 | 程序运行时加载 DLL | +| 生成结果 | 最终 `.exe` 中包含库代码 | `.exe` 运行时依赖 `.dll` | +| 发布文件 | 通常只需要 `.exe` | 需要 `.exe` 和 `.dll` 一起发布 | +| 更新方式 | 修改库后需要重新链接程序 | 替换 DLL 后可能不需要重新编译主程序 | +| 源码隐藏 | 可以隐藏源码 | 可以隐藏源码 | +| 运行依赖 | 不依赖单独 DLL | 运行时必须能找到 DLL | + +简单理解: + +```text +静态库:编译链接时放进 exe 里面。 +动态库:运行时由 exe 去加载 dll。 +``` + +### 3.4 头文件、库文件、可执行文件的关系 + +调用库时通常需要理解三个文件的作用: + +| 文件 | 作用 | +|---|---| +| `.h` | 告诉编译器有哪些函数可以调用 | +| `.lib` | 告诉链接器函数实现在哪里,或者如何导入 DLL 中的函数 | +| `.dll` | 动态库真正运行时被加载的文件 | +| `.exe` | 最终运行的主程序 | + +以调用一个数学库为例: + +```text +MathLib.h 提供函数声明 +MathLib.lib 提供静态库实现,或者提供 DLL 导入信息 +MathDll.dll 动态库真正运行文件 +MainApp.exe 调用库的主程序 +``` + +编译、链接、运行的关系如下: + +```text +编译阶段:检查头文件和函数声明 +链接阶段:检查 .lib 和函数实现 +运行阶段:如果使用 DLL,需要能找到 .dll 文件 +``` + +## 4. 静态库 + +### 4.1 静态库的基本概念 + +静态库是在链接阶段被合并进最终程序的库文件。在 VS2019 中,静态库文件通常以 `.lib` 为后缀。 + +静态库本身不能直接运行,因为它不是完整程序,没有 `main` 函数。它的作用是把一部分功能代码编译成库,供其他应用程序工程调用。 + +例如: + +```text +MathLib.lib +``` + +可以提供如下函数: + +```cpp +int Add(int a, int b); +int Sub(int a, int b); +``` + +主程序工程通过包含头文件并链接 `MathLib.lib` 后,就可以调用 `Add` 和 `Sub` 函数。 + +### 4.2 静态库的生成 + +#### 4.2.1 创建静态库项目 + +操作步骤: + +1. 打开 `Visual Studio 2019`; +2. 点击 `创建新项目`; +3. 搜索并选择 `空项目`; +4. 输入项目名称,例如: + + ```text + MathLib + ``` + +5. 创建项目后,右键项目,选择 `属性`; +6. 进入: + + ```text + 配置属性 -> 常规 -> 配置类型 + ``` + +7. 将配置类型设置为: + + ```text + 静态库(.lib) + ``` + +8. 点击 `应用`,然后点击 `确定`。 + +![创建静态库项目](pictures/Library_005_创建静态库项目.png) + +#### 4.2.2 编写静态库头文件 + +在静态库项目中添加头文件 `MathLib.h`: + +```cpp +#ifndef MATH_LIB_H +#define MATH_LIB_H + +int Add(int a, int b); +int Sub(int a, int b); + +#endif +``` + +头文件的作用是向调用方提供函数声明。调用方包含该头文件后,编译器才能知道 `Add` 和 `Sub` 函数的参数和返回值。 + +![静态库头文件](pictures/Library_006_静态库头文件.png) + +#### 4.2.3 编写静态库源文件 + +在静态库项目中添加源文件 `MathLib.cpp`: + +```cpp +#include "MathLib.h" + +int Add(int a, int b) +{ + return a + b; +} + +int Sub(int a, int b) +{ + return a - b; +} +``` + +源文件中保存函数的真正实现。生成静态库时,这些函数实现会被编译进 `.lib` 文件中。 + +![静态库源文件](pictures/Library_007_静态库源文件.png) + +#### 4.2.4 生成静态库文件 + +生成步骤: + +1. 在顶部选择正确配置,例如 `Debug` 或 `Release`; +2. 选择正确平台,例如 `Win32` 或 `x64`; +3. 点击菜单: + + ```text + 生成 -> 生成解决方案 + ``` + +4. 或使用快捷键: + + ```text + Ctrl + Shift + B + ``` + +5. 生成成功后,可以在输出目录中找到: + + ```text + MathLib.lib + ``` + +默认情况下,常见输出目录可能类似: + +```text +项目目录\Debug\MathLib.lib +项目目录\x64\Debug\MathLib.lib +``` + +具体路径以项目属性中的输出目录为准。 + +![生成静态库](pictures/Library_007_生成静态库.png) + +### 4.3 静态库的调用 + +静态库调用方通常是一个应用程序项目,例如 `MainApp.exe`。调用静态库需要完成三件事: + +```text +包含头文件 +配置库文件路径 +配置库文件名称 +``` + +![静态库调用流程](pictures/Library_008_调用静态库配置.png) + +#### 4.3.1 在工程配置中调用静态库 + +假设静态库文件结构如下: + +```text +LibraryDemo +|---MathLib +| |---MathLib.h +| +---MathLib.lib +| ++---MainApp + +---main.cpp +``` + +调用方工程需要完成以下配置。 + +第一步,配置头文件路径: + +```text +项目属性 -> 配置属性 -> C/C++ -> 常规 -> 附加包含目录 +``` + +添加 `MathLib.h` 所在目录,例如: + +```text +$(SolutionDir)MathLib +``` + +第二步,配置库文件路径: + +```text +项目属性 -> 配置属性 -> 链接器 -> 常规 -> 附加库目录 +``` + +添加 `MathLib.lib` 所在目录,例如: + +```text +$(SolutionDir)MathLib\$(Platform)\$(Configuration) +``` + +![工程配置调用静态库](pictures/Library_008_调用静态库配.png) + +第三步,配置库文件名称: + +```text +项目属性 -> 配置属性 -> 链接器 -> 输入 -> 附加依赖项 +``` + +添加: + +```text +MathLib.lib +``` + +![工程配置调用静态库](pictures/Library_008_调用静态库.png) + +#### 4.3.2 在代码中调用静态库函数 + +主程序 `main.cpp` 示例: + +```cpp +#include +#include "MathLib.h" + +int main(void) +{ + int ret1 = Add(1, 2); + int ret2 = Sub(5, 3); + + printf("ret1 = %d\n", ret1); + printf("ret2 = %d\n", ret2); + + return 0; +} +``` + +运行结果应为: + +```text +ret1 = 3 +ret2 = 2 +``` + +![代码调用静态库](pictures/Library_009_代码调用静态.png) + +#### 4.3.3 在代码中使用语句加载 lib 调用 + +除了在工程属性中填写 `附加依赖项`,也可以在代码中使用 `#pragma comment(lib, "xxx.lib")` 指定需要链接的库文件。 + +示例: + +```cpp +#include +#include "MathLib.h" + +#pragma comment(lib, "MathLib.lib") + +int main(void) +{ + int ret = Add(1, 2); + printf("ret = %d\n", ret); + return 0; +} +``` + +需要注意:`#pragma comment(lib, "MathLib.lib")` 只指定库文件名,库文件所在目录仍然需要通过 `附加库目录` 配置,或者将 `.lib` 放到链接器可以搜索到的位置。 + +也就是说,即使使用代码语句加载 `.lib`,通常仍然需要配置: + +```text +项目属性 -> 配置属性 -> 链接器 -> 常规 -> 附加库目录 +``` + +### 4.4 静态库调用注意事项 + +静态库调用时需要重点注意以下几点: + +1. 主程序和静态库的平台必须一致,例如都为 `Win32` 或都为 `x64`; +2. 主程序和静态库的运行库配置建议保持一致,例如都使用 `/MDd` 或都使用 `/MD`; +3. 头文件路径配置的是目录,不是具体 `.h` 文件; +4. 附加库目录配置的是 `.lib` 所在文件夹,不是 `.lib` 文件本身; +5. 附加依赖项中填写的是具体 `.lib` 文件名; +6. 如果函数只有声明没有实现,会出现无法解析外部符号; +7. 如果 `.cpp` 没有加入静态库项目,也可能导致函数无法解析。 + +常见错误对应关系如下: + +| 问题 | 优先检查内容 | +|---|---| +| 找不到头文件 | 附加包含目录 | +| 找不到 `.lib` | 附加库目录、附加依赖项 | +| 函数无法解析 | 函数实现、源文件是否加入项目、库是否链接 | +| x64 正常但 Win32 异常 | 平台配置是否一致 | +| Debug 正常但 Release 异常 | Debug/Release 属性是否都配置 | + +## 5. 动态库 + +### 5.1 动态库的基本概念 + +动态库是在程序运行时被加载的库文件。在 Windows 平台下,动态库文件通常以 `.dll` 为后缀。 + +在 VS2019 中生成动态库时,通常会生成两个重要文件: + +```text +MathDll.dll +MathDll.lib +``` + +其中: + +| 文件 | 作用 | +|---|---| +| `.dll` | 动态库运行时真正被加载的文件,保存函数实现 | +| `.lib` | 动态库导入库,提供链接阶段所需的导入信息 | + +需要注意:动态库项目生成的 `.lib` 通常不是静态库,它只是导入库。真正运行时仍然需要 `.dll` 文件存在。 + +简单理解: + +```text +静态库的 .lib:保存真正实现,链接进 exe。 +动态库的 .lib:保存导入信息,运行时还要找 dll。 +``` + +### 5.2 动态库的生成 + +动态库生成方式主要有两种: + +1. 通过导出语句生成; +2. 通过模块定义文件 `.def` 生成。 + +### 5.3 通过导出语句生成动态库 + +#### 5.3.1 创建动态库项目 + +操作步骤: + +1. 打开 `Visual Studio 2019`; +2. 创建一个空项目,例如: + + ```text + MathDll + ``` + +3. 右键项目,选择 `属性`; +4. 进入: + + ```text + 配置属性 -> 常规 -> 配置类型 + ``` + +5. 将配置类型设置为: + + ```text + 动态库(.dll) + ``` + +6. 点击 `应用`,然后点击 `确定`。 + +![创建动态库项目](pictures/Library_009_创建动态库项目.png) + +#### 5.3.2 编写导出宏 + +添加头文件 `MathDll.h`: + +```cpp +#ifndef MATH_DLL_H +#define MATH_DLL_H + +#ifdef MATHDLL_EXPORTS +#define MATHDLL_API __declspec(dllexport) +#else +#define MATHDLL_API __declspec(dllimport) +#endif + +extern "C" MATHDLL_API int Add(int a, int b); +extern "C" MATHDLL_API int Sub(int a, int b); + +#endif +``` + +说明: + +| 内容 | 说明 | +|---|---| +| `__declspec(dllexport)` | 生成 DLL 时导出函数 | +| `__declspec(dllimport)` | 调用 DLL 时导入函数 | +| `extern "C"` | 防止 C++ 函数名修饰,方便按函数名查找 | +| `MATHDLL_EXPORTS` | 用于区分当前是生成 DLL 还是调用 DLL | + +在 DLL 项目中需要定义宏: + +```text +MATHDLL_EXPORTS +``` + +通常配置位置为: + +```text +项目属性 -> 配置属性 -> C/C++ -> 预处理器 -> 预处理器定义 +``` + +![动态库导出宏](pictures/Library_010_导出语句生成动态库.png) + +#### 5.3.3 编写动态库源文件 + +添加源文件 `MathDll.cpp`: + +```cpp +#include "MathDll.h" + +int Add(int a, int b) +{ + return a + b; +} + +int Sub(int a, int b) +{ + return a - b; +} +``` + +生成动态库后,`Add` 和 `Sub` 函数会被导出,其他程序可以调用它们。 + +![动态库源文件](pictures/Library_010_动态库.png) + +#### 5.3.4 生成动态库文件 + +点击: + +```text +生成 -> 生成解决方案 +``` + +或使用快捷键: + +```text +Ctrl + Shift + B +``` + +生成成功后,通常可以得到: + +```text +MathDll.dll +MathDll.lib +``` + +其中 `.dll` 是运行时文件,`.lib` 是导入库。 + +![动态库源文件](pictures/Library_010_动态库源文件.png) + +### 5.4 通过模块定义文件生成动态库 + +模块定义文件通常以 `.def` 为后缀,用于明确指定动态库导出的函数名称。 + +#### 5.4.1 编写 DLL 头文件 + +`MathDllDef.h` 示例: + +```cpp +#ifndef MATH_DLL_DEF_H +#define MATH_DLL_DEF_H + +extern "C" int Add(int a, int b); +extern "C" int Sub(int a, int b); + +#endif +``` + +这里不使用 `__declspec(dllexport)`,而是通过 `.def` 文件指定导出函数。 + +![DEF头文件](pictures/Library_011_DEF文件生成动态库.png) + +#### 5.4.2 编写 DLL 源文件 + +`MathDllDef.cpp` 示例: + +```cpp +#include "MathDllDef.h" + +int Add(int a, int b) +{ + return a + b; +} + +int Sub(int a, int b) +{ + return a - b; +} +``` + +![DEF源文件](pictures/Library_011_DEF源文件.png) + +#### 5.4.3 添加模块定义文件 + +添加文件 `MathDllDef.def`: + +```text +LIBRARY "MathDllDef" + +EXPORTS + Add + Sub +``` + +`.def` 文件中: + +| 关键字 | 说明 | +|---|---| +| `LIBRARY` | 指定生成的 DLL 名称 | +| `EXPORTS` | 指定需要导出的函数列表 | + +然后在项目属性中配置模块定义文件: + +```text +项目属性 -> 配置属性 -> 链接器 -> 输入 -> 模块定义文件 +``` + +填写: + +```text +MathDllDef.def +``` + +![模块定义文件配置](pictures/Library_011_DEF文件生成动态库.png) + +#### 5.4.4 DEF 文件方式注意事项 + +使用 `.def` 文件导出函数时需要注意: + +1. `.def` 文件中的函数名必须和实际函数名对应; +2. C++ 函数可能存在函数名修饰,建议配合 `extern "C"` 使用; +3. `.def` 文件适合集中管理导出函数; +4. 如果导出函数较多,`.def` 文件比在每个函数前写导出语句更清晰。 + +![DEF注意事项](pictures/Library_011_DEF注意事项.png) + +### 5.5 动态库的调用 + +动态库的调用方式主要有三种: + +1. 在工程配置中调用; +2. 在代码中使用语句加载 `.lib` 调用; +3. 在代码中使用语句加载 `.dll` 调用。 + +![动态库调用方式](pictures/Library_012_动态库调用配置.png) + +### 5.6 在工程配置中调用动态库 + +这种方式也叫隐式链接。调用方在编译链接阶段链接动态库生成的 `.lib` 导入库,程序运行时再加载 `.dll`。 + +调用方需要完成三件事: + +```text +包含头文件 +配置导入库 .lib 路径 +配置导入库 .lib 名称 +``` + +第一步,配置头文件路径: + +```text +项目属性 -> 配置属性 -> C/C++ -> 常规 -> 附加包含目录 +``` + +第二步,配置 `.lib` 所在目录: + +```text +项目属性 -> 配置属性 -> 链接器 -> 常规 -> 附加库目录 +``` + +第三步,配置 `.lib` 文件名: + +```text +项目属性 -> 配置属性 -> 链接器 -> 输入 -> 附加依赖项 +``` + +例如: + +```text +MathDll.lib +``` + +第四步,保证运行时能找到 `.dll` 文件。常见做法是将 DLL 放到: + +```text +主程序 exe 同级目录 +``` + +主程序示例: + +```cpp +#include +#include "MathDll.h" + +int main(void) +{ + int ret = Add(1, 2); + printf("ret = %d\n", ret); + return 0; +} +``` + +![工程配置调用动态库](pictures/Library_012_动态库调用配置.png) + +### 5.7 在代码中语句加载 lib 调用动态库 + +调用动态库时,也可以在代码中使用 `#pragma comment(lib, "xxx.lib")` 指定导入库。 + +示例: + +```cpp +#include +#include "MathDll.h" + +#pragma comment(lib, "MathDll.lib") + +int main(void) +{ + int ret = Add(1, 2); + printf("ret = %d\n", ret); + return 0; +} +``` + +需要注意: + +1. 这里加载的是动态库的导入库 `.lib`; +2. 运行时仍然需要 `MathDll.dll`; +3. `.dll` 一般需要放到 `.exe` 同级目录; +4. 如果找不到 DLL,程序运行时会报错,而不是编译时报错。 + +![代码加载lib调用动态库](pictures/Library_013_代码加载lib调用动态库.png) + +### 5.8 在代码中语句加载 dll 调用动态库 + +这种方式也叫显式加载,程序运行过程中主动调用 Windows API 加载 DLL。 + +常用函数包括: + +| 函数 | 说明 | +|---|---| +| `LoadLibrary` | 加载 DLL | +| `GetProcAddress` | 获取 DLL 中导出函数地址 | +| `FreeLibrary` | 释放 DLL | + +示例代码: + +```cpp +#include +#include + +typedef int (*PFN_ADD)(int, int); + +int main(void) +{ + HMODULE hDll = LoadLibraryA("MathDll.dll"); + if (hDll == NULL) + { + printf("LoadLibrary failed\n"); + return -1; + } + + PFN_ADD pAdd = (PFN_ADD)GetProcAddress(hDll, "Add"); + if (pAdd == NULL) + { + printf("GetProcAddress failed\n"); + FreeLibrary(hDll); + return -1; + } + + int ret = pAdd(1, 2); + printf("ret = %d\n", ret); + + FreeLibrary(hDll); + return 0; +} +``` + +这种方式不需要在链接器中配置 `MathDll.lib`,但必须保证运行时能找到 `MathDll.dll`,并且函数名和导出名一致。 + +### 5.9 动态库调用注意事项 + +动态库调用时需要注意以下几点: + +1. 使用工程配置方式调用 DLL 时,需要 `.h`、`.lib` 和 `.dll`; +2. `.lib` 用于链接阶段,`.dll` 用于运行阶段; +3. DLL 找不到通常是运行时错误; +4. 主程序和 DLL 的平台必须一致; +5. 函数导出名必须正确; +6. C++ 函数建议使用 `extern "C"` 避免函数名被修饰; +7. DLL 可以放在 exe 同级目录,也可以放在系统 PATH 能找到的目录; +8. 显式加载 DLL 时,要检查 `LoadLibrary` 和 `GetProcAddress` 的返回值。 + +![动态库调用注意事项](pictures/Library_015_动态库调用注意事项.png) + +## 6. 静态库和动态库对比总结 + +静态库和动态库都是代码复用的重要方式,但使用场景不同。 + +| 对比项 | 静态库 | 动态库 | +|---|---|---| +| 主要文件 | `.lib` | `.dll` 和 `.lib` | +| 是否可直接运行 | 不可以 | 不可以 | +| 是否需要主程序 | 需要 | 需要 | +| 链接方式 | 链接阶段合入程序 | 链接导入库,运行时加载 DLL | +| 运行依赖 | 通常不依赖额外 DLL | 必须能找到 DLL | +| 更新方式 | 更新库后通常要重新链接主程序 | 可通过替换 DLL 更新实现 | +| 适合场景 | 稳定模块、简单发布 | 插件、模块更新、运行时加载 | + +简单选择原则: + +```text +想让程序发布简单,可以优先考虑静态库。 +想让模块可以单独更新,可以考虑动态库。 +想运行时决定是否加载某个模块,可以使用 LoadLibrary 显式加载 DLL。 +``` + +## 7. 常见问题和排查方法 + +### 7.1 找不到头文件 + +常见报错: + +```text +无法打开包括文件: “xxx.h”: No such file or directory +``` + +原因通常是 `附加包含目录` 没有配置,或者 `#include` 路径写错。 + +优先检查: + +```text +项目属性 -> 配置属性 -> C/C++ -> 常规 -> 附加包含目录 +``` + +### 7.2 找不到 lib 文件 + +常见报错: + +```text +LINK : fatal error LNK1104: 无法打开文件 “xxx.lib” +``` + +原因通常是 `.lib` 文件路径错误,或者附加依赖项中库文件名写错。 + +优先检查: + +```text +项目属性 -> 配置属性 -> 链接器 -> 常规 -> 附加库目录 +项目属性 -> 配置属性 -> 链接器 -> 输入 -> 附加依赖项 +``` + +### 7.3 无法解析的外部符号 + +常见报错: + +```text +error LNK2019: 无法解析的外部符号 +``` + +可能原因: + +1. 函数只有声明,没有实现; +2. 实现函数的源文件没有加入项目; +3. 没有链接正确的库文件; +4. C/C++ 混合调用时函数名不一致; +5. 平台或配置不一致。 + +### 7.4 运行时找不到 DLL + +常见现象:程序编译和链接成功,但运行时提示找不到 DLL。 + +可能原因: + +1. DLL 没有放到 exe 同级目录; +2. DLL 不在系统 PATH 路径中; +3. Debug/Release 使用了不同输出路径; +4. Win32/x64 平台 DLL 放错; +5. DLL 文件名和程序依赖名不一致。 + +常见解决方法: + +```text +将 DLL 放到 exe 所在目录。 +``` + +### 7.5 平台不一致 + +如果主程序是 x64,而库文件是 Win32,通常会出现链接错误或运行异常。 + +排查时需要确认: + +```text +主程序平台 = 库文件平台 = DLL 平台 +``` + +例如都使用: + +```text +x64 Debug +``` + +或者都使用: + +```text +Win32 Debug +``` + +## 8. 学习练习 + +### 8.1 练习一:生成静态库并调用 + +练习目标:掌握静态库的生成和工程配置调用方法。 + +要求: + +1. 创建静态库项目 `MathLib`; +2. 添加 `MathLib.h` 和 `MathLib.cpp`; +3. 实现 `Add` 和 `Sub` 函数; +4. 生成 `MathLib.lib`; +5. 创建主程序项目 `MainApp`; +6. 在主程序中配置头文件路径、库文件路径和库文件名称; +7. 调用 `Add` 函数并输出结果。 + +### 8.2 练习二:通过 pragma 调用静态库 + +练习目标:掌握通过代码语句指定 `.lib` 的方法。 + +要求: + +1. 在主程序中包含静态库头文件; +2. 使用: + + ```cpp + #pragma comment(lib, "MathLib.lib") + ``` + +3. 配置附加库目录; +4. 调用库函数并运行程序。 + +### 8.3 练习三:通过导出语句生成动态库并调用 + +练习目标:掌握 `__declspec(dllexport)` 和 `__declspec(dllimport)` 的使用。 + +要求: + +1. 创建动态库项目 `MathDll`; +2. 编写导出宏; +3. 导出 `Add` 函数; +4. 生成 `MathDll.dll` 和 `MathDll.lib`; +5. 创建主程序项目; +6. 配置头文件路径、导入库路径和导入库名称; +7. 将 DLL 放到 exe 同级目录; +8. 调用 `Add` 函数并运行。 + +### 8.4 练习四:通过 DEF 文件生成动态库并调用 + +练习目标:掌握 `.def` 文件导出函数的方法。 + +要求: + +1. 创建动态库项目; +2. 添加 `.def` 文件; +3. 在 `.def` 文件中写入 `EXPORTS`; +4. 配置模块定义文件; +5. 生成 DLL; +6. 在主程序中调用 DLL 导出的函数。 + +### 8.5 练习五:使用 LoadLibrary 加载 DLL + +练习目标:掌握运行时显式加载 DLL 的方法。 + +要求: + +1. 准备一个已经生成的 DLL; +2. 在主程序中调用 `LoadLibraryA`; +3. 使用 `GetProcAddress` 获取函数地址; +4. 通过函数指针调用 DLL 函数; +5. 使用 `FreeLibrary` 释放 DLL。 + +## 9. 验收重点总结 + +Library 部分验收时,应重点掌握以下内容: + +| 分类 | 必须掌握内容 | +|---|---| +| 静态库概念 | 理解静态库 `.lib` 的作用 | +| 静态库生成 | 能够创建静态库项目并生成 `.lib` | +| 静态库调用 | 能够通过工程配置调用静态库 | +| 代码加载 lib | 能够使用 `#pragma comment(lib, "xxx.lib")` | +| 动态库概念 | 理解 `.dll` 和导入库 `.lib` 的关系 | +| 导出语句 | 能够使用 `__declspec(dllexport)` 导出函数 | +| 模块定义文件 | 能够使用 `.def` 文件导出函数 | +| 动态库工程配置调用 | 能够配置头文件路径、导入库路径和导入库名称 | +| 代码加载 DLL | 能够使用 `LoadLibrary` 和 `GetProcAddress` 调用 DLL | +| 错误排查 | 能够排查找不到头文件、找不到 lib、找不到 DLL、平台不一致等问题 | + +## 10. 学习结论 + +通过本次学习,掌握了 VS2019 平台下静态库和动态库的基本概念、生成方法和调用方法。 + +静态库适合将稳定代码直接链接进主程序,发布简单,运行时不需要额外 DLL。动态库适合需要运行时加载、模块独立更新或隐藏实现的场景,但发布时需要保证 DLL 能被程序找到。 + +在实际使用中,需要重点理解以下关系: + +```text +.h 文件:提供接口声明 +.lib 文件:静态库实现,或动态库导入信息 +.dll 文件:动态库运行时真正加载的实现 +.exe 文件:调用库的主程序 +``` + +库调用问题排查时,应按照以下顺序分析: + +```text +编译阶段找不到 .h -> 检查附加包含目录 +链接阶段找不到 .lib -> 检查附加库目录和附加依赖项 +链接阶段函数无法解析 -> 检查函数实现和库是否正确链接 +运行阶段找不到 .dll -> 检查 DLL 是否在 exe 同级目录或 PATH 中 +平台相关错误 -> 检查 Win32/x64 是否一致 +``` + +掌握库的生成与使用后,可以更好地理解公司项目中模块封装、源码隐藏、库文件发布和多人协作的基本方式。 diff --git a/yuwenhao/Library/pictures/Library_005_创建静态库项目.png b/yuwenhao/Library/pictures/Library_005_创建静态库项目.png new file mode 100644 index 0000000..d1924c5 Binary files /dev/null and b/yuwenhao/Library/pictures/Library_005_创建静态库项目.png differ diff --git a/yuwenhao/Library/pictures/Library_006_静态库头文件.png b/yuwenhao/Library/pictures/Library_006_静态库头文件.png new file mode 100644 index 0000000..cb07c46 Binary files /dev/null and b/yuwenhao/Library/pictures/Library_006_静态库头文件.png differ diff --git a/yuwenhao/Library/pictures/Library_007_生成静态库.png b/yuwenhao/Library/pictures/Library_007_生成静态库.png new file mode 100644 index 0000000..6e2b17e Binary files /dev/null and b/yuwenhao/Library/pictures/Library_007_生成静态库.png differ diff --git a/yuwenhao/Library/pictures/Library_007_静态库源文件.png b/yuwenhao/Library/pictures/Library_007_静态库源文件.png new file mode 100644 index 0000000..e36d8bd Binary files /dev/null and b/yuwenhao/Library/pictures/Library_007_静态库源文件.png differ diff --git a/yuwenhao/Library/pictures/Library_008_调用静态库.png b/yuwenhao/Library/pictures/Library_008_调用静态库.png new file mode 100644 index 0000000..332d6a1 Binary files /dev/null and b/yuwenhao/Library/pictures/Library_008_调用静态库.png differ diff --git a/yuwenhao/Library/pictures/Library_008_调用静态库配.png b/yuwenhao/Library/pictures/Library_008_调用静态库配.png new file mode 100644 index 0000000..a7f8d30 Binary files /dev/null and b/yuwenhao/Library/pictures/Library_008_调用静态库配.png differ diff --git a/yuwenhao/Library/pictures/Library_008_调用静态库配置.png b/yuwenhao/Library/pictures/Library_008_调用静态库配置.png new file mode 100644 index 0000000..00b84e2 Binary files /dev/null and b/yuwenhao/Library/pictures/Library_008_调用静态库配置.png differ diff --git a/yuwenhao/Library/pictures/Library_009_代码调用静态.png b/yuwenhao/Library/pictures/Library_009_代码调用静态.png new file mode 100644 index 0000000..876798d Binary files /dev/null and b/yuwenhao/Library/pictures/Library_009_代码调用静态.png differ diff --git a/yuwenhao/Library/pictures/Library_009_创建动态库项目.png b/yuwenhao/Library/pictures/Library_009_创建动态库项目.png new file mode 100644 index 0000000..93251f8 Binary files /dev/null and b/yuwenhao/Library/pictures/Library_009_创建动态库项目.png differ diff --git a/yuwenhao/Library/pictures/Library_010_动态库.png b/yuwenhao/Library/pictures/Library_010_动态库.png new file mode 100644 index 0000000..276248a Binary files /dev/null and b/yuwenhao/Library/pictures/Library_010_动态库.png differ diff --git a/yuwenhao/Library/pictures/Library_010_动态库源文件.png b/yuwenhao/Library/pictures/Library_010_动态库源文件.png new file mode 100644 index 0000000..e65f991 Binary files /dev/null and b/yuwenhao/Library/pictures/Library_010_动态库源文件.png differ diff --git a/yuwenhao/Library/pictures/Library_010_导出语句生成动态库.png b/yuwenhao/Library/pictures/Library_010_导出语句生成动态库.png new file mode 100644 index 0000000..98a8e07 Binary files /dev/null and b/yuwenhao/Library/pictures/Library_010_导出语句生成动态库.png differ diff --git a/yuwenhao/Library/pictures/Library_012_动态库调用配置.png b/yuwenhao/Library/pictures/Library_012_动态库调用配置.png new file mode 100644 index 0000000..46c047d Binary files /dev/null and b/yuwenhao/Library/pictures/Library_012_动态库调用配置.png differ diff --git a/yuwenhao/Library/pictures/Library_013_代码加载lib调用动态库.png b/yuwenhao/Library/pictures/Library_013_代码加载lib调用动态库.png new file mode 100644 index 0000000..16d5e57 Binary files /dev/null and b/yuwenhao/Library/pictures/Library_013_代码加载lib调用动态库.png differ diff --git a/yuwenhao/UnitTest/UnitTestVisualStudio.md b/yuwenhao/UnitTest/UnitTestVisualStudio.md new file mode 100644 index 0000000..e69de29