---
title: "Windows链接"
author: "Perrin Yong"
author_profile: https://www.pystone.net/profile/
published_by: "Perrin Yong"
canonical: https://www.pystone.net/notes/cpp-windows-linking/
type: note
content_role: unspecified
visibility: public
id_stability: rename-stable
source_path: "10-计算机、信息技术与工程/02-编程语言与运行时/C++/Windows链接.md"
content_hash: 8dc29a62f1c0770a1ce036365de8a80c31f54f880ecb0449a5418a292c2a450a
knowledge_version: 224c990773de.5fa8af6e39fa
site_commit: 224c990773de166d23a886306577dd90379529ce
notes_commit: 5fa8af6e39fa3891d1b9b4832bfa6c4e0ecaaf0a
---
# Windows链接

﻿# Windows链接


1. 静态链接（static）

* `.lib`（静态库） → 代码被拷进最终产物
* 不产生对该库 DLL 的运行时依赖

2. 动态链接（dynamic）分两种

* 隐式动态链接：`.lib`（导入库） + `.dll`（运行时） → 产物导入表依赖 DLL
* 显式动态链接：运行时 `LoadLibrary/GetProcAddress` → 不在导入表里硬依赖
*
## 1）从“编译到运行”的链路全貌

### A. 编译（Compile）

* 输入：`.c/.cpp`
* 输出：`.obj`（目标文件）
* 里面有什么：

  * 机器码（函数实现）
  * 符号表（这个 obj 里定义了哪些函数/变量、引用了哪些外部符号）
* 此时 **还没有把所有外部函数地址确定**，比如你调用 `GetProcessMemoryInfo`，编译器只会记一个“外部符号引用”。

### B. 链接（Link）

* 输入：很多 `.obj` + 若干 `.lib`（库）+ 以及一些默认系统库
* 输出：`.exe` 或 `.dll`
* 链接器做两件大事：

  1. **符号解析**：把“外部符号引用”匹配到“某个地方的定义”
  2. **地址重定位**：把最终要调用/访问的地址关系安排好

链接有两条主要路径：

#### 1）静态链接（Static link）

* 你链接到的是 **静态库 .lib（里面真的包含实现代码）**
* 链接器会把需要的 `.obj` 代码**复制**到最终 exe/dll 里
* 结果：

  * 最终产物 **不需要那个库的 DLL**（因为代码已经被带进来）
  * 文件可能更大
  * 每个使用方都有一份拷贝（除非 LTO/COMDAT 合并等优化）

#### 2）动态链接（Dynamic link，基于 DLL）

动态链接又分两种“建立依赖”的方式：

* **隐式动态链接（Implicit dynamic linking）**

  * 你在链接时使用一个 **导入库 import lib（也是 .lib）**
  * 链接器不会把实现代码拷进来，而是生成 **Import Table（导入表）**
  * 程序启动/模块加载时，Windows Loader 会加载对应 `.dll` 并把函数地址填入 IAT
  * 如果 DLL 缺失，可能导致**加载失败/进程启动失败**（取决于该模块什么时候被加载）

* **显式动态链接（Explicit dynamic linking）**

  * 运行时你自己调用：

    * `LoadLibrary("xxx.dll")`
    * `GetProcAddress("Func")`
  * 这时链接器不需要 import lib（或只需要 kernel32 的这些 API）
  * 如果 DLL 缺失，你可以选择**优雅降级**，不会因为导入表缺失而加载失败

---

## 2）“库文件”到底分哪些？（Windows 视角）

### 1）DLL：动态链接库（运行时实体）

* `.dll` 是真正的动态库文件，装着实现代码
* 运行时会被加载到进程地址空间里

### 2）LIB：`.lib` 不是一种“库类型”，而是两种不同东西的容器名（关键）

在 MSVC/Windows 生态里，`.lib` 可能是：

#### A) 静态库（static library）

* 里面包含真实的 `.obj` 代码（实现）
* 用于静态链接：代码会被拷进最终 exe/dll

#### B) 导入库（import library）

* 里面**通常不包含实现代码**（或只包含很小的跳板/描述信息）
* 它告诉链接器：某些符号来自某个 DLL
* 用于隐式动态链接：最终 exe/dll 里会生成导入表，运行时需要对应 DLL

> 所以：**“.lib 既可能是静态库，也可能是导入库”**
> 光看扩展名看不出来，必须结合来源/用途/工具检查（dumpbin 等）。

---


## Android与Windows的区别
是的，这里确实体现了 **Windows vs Android 在“动态链接”工程化形态上的差异**。但本质机制并不矛盾：两边都可以“链接到动态库”，只是**Windows 把“链接到 DLL”通常分成了两件文件（.lib + .dll）**，而 Android/Linux 通常直接用 `.so` 及其符号/SONAME 体系，不需要“import lib”这种额外壳。

下面按你关心的点说明：**import lib 是什么、为什么 Android 没这个概念、两边动态链接流程差异、CMakeLists 写法差异**。

---

### 1）为什么 Android 平台很少听说 “import lib”？

#### Android/Linux 的常规做法

* 动态库是 **`.so`**，里面既有实现代码，也包含**动态符号表**（可供其他模块在链接/运行时解析）。
* 链接一个共享库时，链接器用的是：

  * `libxxx.so`（或 `libxxx.so` 的 *linker stub*）
  * 生成可执行文件/共享库里的 **DT_NEEDED** 依赖条目
* 运行时由 dynamic loader（`linker`/`ld-android.so`）加载 `.so`，按 ELF 机制做重定位与符号解析。

#### Windows 的常规做法

* 动态库是 **`.dll`**，但 MSVC 的链接器通常不直接用 `.dll` 做链接输入。
* MSVC 使用一个配套的 **导入库（import library）`xxx.lib`**：

  * 它像“链接期说明书”，告诉链接器：这些符号来自哪个 DLL、如何生成导入表（IAT）。
* 运行时由 Windows Loader 根据导入表加载 `.dll` 并填充 IAT。


### 2）两边动态链接的“工程形态”差异总结

#### Windows（PE/COFF）

* **链接期**：通常用 `xxx.lib`（import lib）来链接
* **运行期**：加载 `xxx.dll`
* **显式动态加载**：`LoadLibrary` + `GetProcAddress`

#### Android/Linux（ELF）

* **链接期**：用 `libxxx.so`（或开发包提供的 stub so）
* **运行期**：加载 `libxxx.so`
* **显式动态加载**：`dlopen` + `dlsym`

---

### 3）CMakeLists 写法有什么差异？

#### A. 链接一个“系统库/平台库”的差异

**Windows：**

```cmake
target_link_libraries(my_target PRIVATE psapi)
```

* 这会让链接器去找 `psapi.lib`（import lib），最终运行时依赖 `psapi.dll`。
* 如果你写了 `#pragma comment(lib,"psapi.lib")`，那是 MSVC 私有的做法；CMake 更推荐用 `target_link_libraries`。

**Android：**

```cmake
target_link_libraries(my_target PRIVATE log android)
```

* Android NDK 里 `log` / `android` 等是系统 so（实际是 `liblog.so` / `libandroid.so`）。
* 不存在“额外 import lib”的概念。

---

#### B. 链接一个第三方动态库的差异（你自己带的库）

**Android：**
你通常直接链接 `.so`：

```cmake
add_library(foo SHARED IMPORTED)
set_target_properties(foo PROPERTIES
  IMPORTED_LOCATION "${CMAKE_SOURCE_DIR}/libs/${ANDROID_ABI}/libfoo.so"
)
target_link_libraries(my_target PRIVATE foo)
```

**Windows：**
第三方库通常给你一套：`foo.dll + foo.lib`

* 你链接的是 `.lib`，运行时需要 `.dll`：

```cmake
add_library(foo SHARED IMPORTED)
set_target_properties(foo PROPERTIES
  IMPORTED_IMPLIB  "${CMAKE_SOURCE_DIR}/libs/foo.lib"   # 链接期用
  IMPORTED_LOCATION "${CMAKE_SOURCE_DIR}/bin/foo.dll"   # 运行时用（可选写）
)
target_link_libraries(my_target PRIVATE foo)
```

> 关键差别：Windows 下 CMake 要区分 `IMPORTED_IMPLIB`（导入库）和 `IMPORTED_LOCATION`（DLL 本体）。Android 下通常只要 `IMPORTED_LOCATION` 指向 `.so`。

---

#### C. “我不想硬依赖某个动态库”的写法差异（SDK 很常用）

**Windows 显式加载：**

* 不在 CMake 链接 `psapi`（也不 `#pragma comment(lib)`）
* 运行时：

  * `LoadLibraryW(L"psapi.dll")`
  * `GetProcAddress(...)`
* 好处：缺失时可降级，不影响模块加载/进程启动。

**Android 显式加载：**

* 不在 CMake 链接 `libxxx.so`
* 运行时：

  * `dlopen("libxxx.so", RTLD_NOW)`
  * `dlsym(...)`
* 同样是可降级策略。

---
