# 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 <stdio.h>
#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 <stdio.h>
#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 <stdio.h>
#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 <stdio.h>
#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 <stdio.h>
#include <windows.h>

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 是否一致
```

掌握库的生成与使用后，可以更好地理解公司项目中模块封装、源码隐藏、库文件发布和多人协作的基本方式。
