🎯 导读:库(Library)是 C++ 工程化的基石。本文用 🎭 生活比喻、📊 对比表格 和 💻 完整代码,带你彻底搞懂静态库 vs 动态库的区别,以及 Windows 下那个让无数人头疼的「导出宏」
FITKAppFrameworkAPI到底是什么。读完你就能独立搭建一个带库的跨平台项目!
🏛️ 前言:为什么要用「库」?
想象你在开一家🍔汉堡店,每个汉堡都需要「酱料」:
- ❌ 不用库:每做一个汉堡,都从头熬一锅酱 —— 重复劳动,代码爆炸
- ✅ 使用库:熬一大桶酱(库),所有汉堡直接取用 —— 复用、解耦、好维护
「库」就是把可复用的代码打包,供多个程序共享。而库又分两大门派:静态库(焊死在身上)和动态库(即插即用)。下面我们逐一拆解 👇
📑 目录
- 静态库 (Static Library)
- 动态库 (Dynamic Library)
- Windows 与 Linux 的差异
- 导出/导入宏(
FITKAppFrameworkAPI) - qmake (.pro) 配置库
- CMake 配置库
- 综合实战:从零搭建带库的项目
🧱 一、静态库 (Static Library)
一句话:把库的代码「复制」进最终的 exe 里,从此合为一体。
🎭 生动比喻:把家具焊死在房子里 🏠
静态库就像装修时把🛋️沙发、🪑桌子焊死在地板上。房子(exe)搬到哪,家具就跟到哪,不依赖外部 —— 但房子也因此变得又大又重,想换张桌子就得砸地板重装。
🔍 是什么
- 🔗 编译时直接把库代码复制进可执行文件(exe)
- 🪟 Windows 后缀:
.lib - 🐧 Linux 后缀:
.a(archive,归档文件) - 📦 最终 exe 较大,但不依赖任何额外文件
✅ 优点
| 优点 | 说明 |
|---|---|
| 🚀 部署简单 | 一个 exe 就能跑,无需附带任何 dll |
| ⚡ 加载快 | 没有运行时加载开销 |
| 🛡️ 不会缺文件 | 启动时绝不会出现「找不到 dll」的报错 |
❌ 缺点
| 缺点 | 说明 |
|---|---|
| 💾 占空间大 | 多个 exe 用同一静态库,会各自重复包含一份代码 |
| 🔧 更新麻烦 | 库改了一行,就要重新编译链接整个 exe |
🔌 二、动态库 (Dynamic Library / Shared Library)
一句话:库单独存放,exe 运行时才去「找」它、加载它。
🎭 生动比喻:可插拔的家用电器 🔋
动态库就像🔌可插拔的电器:电视、冰箱单独摆放,需要时插上电源(运行时加载)即可。换电器只需拔插头(替换 dll),不用动房子;多个房间还能共用一台中央空调(多个 exe 共享一份 dll)。
🔍 是什么
- 📝 编译时只生成「引用信息」,运行时才真正加载
- 🪟 Windows 后缀:
.dll+ 配套的.lib(导入库) - 🐧 Linux 后缀:
.so(shared object) - 🎈 最终 exe 很小,但必须随身携带 dll/so
🪟 Windows 动态库的特殊之处
在 Windows 上,编译一个 dll 会同时吐出两个文件:
| 文件 | 作用 |
|---|---|
🟢 MyLib.dll |
运行时必需的动态库(真正的代码在这里) |
🔵 MyLib.lib |
编译时使用的导入库(只含符号地址,不含代码) |
💡 这里的
.lib只是 dll 的「门牌号 🏷️」,告诉链接器「函数住在哪个 dll 里」,本身不是真正的代码。
所以在本项目(FastCAE-APPStructural)的场景中:
- 📁
FITK_Kernel/每个子目录都编译成一个.dll - 📁 同时在
output/bin下生成对应的.lib(导入库) - 🎯 可执行文件
StructuralApp.exe链接这些.lib,运行时再加载对应的.dll
✅ 优点
| 优点 | 说明 |
|---|---|
| 💾 节省空间 | 多个 exe 共享同一份 dll,不重复 |
| 🔧 独立更新 | 替换 dll 即可升级,无需重新编译 exe |
| 🧩 插件化 | 可在运行时动态加载(QLibrary / dlopen) |
❌ 缺点
| 缺点 | 说明 |
|---|---|
| 😈 DLL Hell | dll 版本不匹配,程序直接崩溃 |
| 🗺️ 部署复杂 | 必须确保 exe 能找到所有 dll |
| 🐢 启动稍慢 | 运行时需要加载并解析符号 |
🏗️ 在本项目中的体现
本项目的库全部编译为动态库(SHARED / DLL),每个子项目一个 dll:
1 | FITKCore.dll + FITKCore.lib (导入库) |
🌍 三、Windows 与 Linux 的差异
| 对比项 | 🪟 Windows | 🐧 Linux |
|---|---|---|
| 动态库后缀 | .dll |
.so |
| 静态库后缀 | .lib |
.a |
| 导入库 | .lib(伴随 dll) |
无(直接链接 .so) |
| 编译时链接的文件 | .lib(导入库) |
.so 或 .a |
| 运行时查找路径 | exe 同级目录、PATH 环境变量 |
LD_LIBRARY_PATH、/usr/lib 等 |
| 导出符号 | 默认不导出 ⚠️,需 __declspec(dllexport) |
默认全部导出 ✅ |
| 导出宏(Qt) | Q_DECL_EXPORT = __declspec(dllexport) |
Q_DECL_EXPORT = 空 / __attribute__((visibility("default"))) |
| 导入宏(Qt) | Q_DECL_IMPORT = __declspec(dllimport) |
Q_DECL_IMPORT = 空 |
🔑 最关键的区别:符号导出
这是本项目大量使用 FITKAppFrameworkAPI 这类宏的根本原因 ——
⚠️ Windows 下,dll 必须明确声明哪些类/函数要「导出」给外界,否则外部程序根本访问不到!
✅ Linux 下,
.so默认所有符号都可见,所以Q_DECL_EXPORT在 Linux 下通常是个空宏。
💡 一句话总结:
FITKAppFrameworkAPI、GUIFRAMEAPI、GUIWIDGETAPI等宏,本质上是为 Windows 的__declspec(dllexport/dllimport)服务的跨平台封装。
🏷️ 四、导出/导入宏(FITKAppFrameworkAPI 这类宏)
🎭 生动比喻:海关的「报关单」📋
把 dll 想象成一个国家🏴。里面的类和函数是「商品」,外面的程序想买(调用),必须先报关导出。FITKAppFrameworkAPI 就是贴在商品上的「报关单」:
- 📤 出口方(编译库本身):盖「EXPORT」章 → 允许出关
- 📥 进口方(使用库的人):盖「IMPORT」章 → 表明这是进口货
同一个宏,靠一个「开关」自动在 EXPORT / IMPORT 之间切换。
4.1 🧩 宏如何定义
本项目每创建一个动态库,就配一个 XXXAPI.h 头文件。例如:
FITKAppFrameworkAPI.h:
1 |
|
其中:
- 📤
Q_DECL_EXPORT→ Windows 下展开为__declspec(dllexport),Linux 下为空 - 📥
Q_DECL_IMPORT→ Windows 下展开为__declspec(dllimport),Linux 下为空
4.2 🎚️ 何时定义,何时不定义
这个「开关」由构建系统控制:
| 场景 | FITKAppFramework_API 状态 |
FITKAppFrameworkAPI 的值 |
|---|---|---|
📤 编译 FITKAppFramework 库本身 |
DEFINES += FITKAppFramework_API |
Q_DECL_EXPORT(这些类要导出给外界) |
📥 编译 StructuralApp.exe 或其它库 |
没有定义 | Q_DECL_IMPORT(这些类来自外部 dll) |
4.3 🛠️ 如何在类上使用
1 | // FITK_Kernel/FITKAppFramework/FITKClassA.h |
这个宏告诉编译器:
- 📤 编译该库时:
FITKClassA的所有成员函数对 dll 外部可见 - 📥 使用该库时:调用其成员函数走 dll 导入(编译器生成更高效的调用代码)
4.4 📇 本项目中所有宏速查
| 宏名称 | 对应库 | 定义文件 |
|---|---|---|
FITKAppFrameworkAPI |
FITKAppFramework | FITK_Kernel/FITKAppFramework/FITKAppFrameworkAPI.h |
FITKCoreAPI |
FITKCore | FITK_Kernel/FITKCore/FITKCoreAPI.h |
FITKAdaptorAPI |
FITKAdaptor | FITK_Kernel/FITKAdaptor/FITKAdaptorAPI.h |
FITKWidgetAPI |
FITKWidget | FITK_Component/FITKWidget/FITKWidgetAPI.h |
GUIFRAMEAPI |
GUIFrame | GUIFrame/GUIFrameAPI.h |
GUIWIDGETAPI |
GUIWidget | GUIWidget/GUIWidgetAPI.h |
GUIDIALOGAPI |
GUIDialog | GUIDialog/GUIDialogAPI.h |
OperatorsInterfaceAPI |
OperatorsInterface | OperatorsInterface/OperatorsInterfaceAPI.h |
| … 共 50+ 个 | … | … |
4.5 💥 如果忘记加宏会怎样?
在 Windows 上编译 dll 时,如果类前忘了加导出宏:
1 | // ❌ 忘写 FITKAppFrameworkAPI |
- 🟡 编译 dll 时:能编译通过,但
FITKClassA的符号全部不导出 - 🔴 链接 exe 时:链接器爆出经典错误 👇
1 | LNK2019: unresolved external symbol |
😱 找不到这个类! 这是 Windows C++ 开发最常见的坑之一,看到
LNK2019先检查导出宏是不是漏了。
🔧 五、qmake (.pro) 中配置库
5.1 📤 创建动态库(lib 项目)
1 | # FITK_Kernel/FITKAppFramework/FITKAppFramework.pro |
⭐
DEFINES += FITKAppFramework_API就是上一节说的那个「开关」,编译该库时把宏切到导出模式。
5.2 🎯 创建可执行文件(app 项目)
1 | # StructuralApp/StructuralApp.pro |
📌 注意:app 项目这里不定义
FITKAppFramework_API,所以宏自动切到Q_DECL_IMPORT(导入模式)。
5.3 🧰 链接第三方库
1 | # GUIFrame/GUIFrame.pro |
5.4 📖 qmake 链接语法总结
| 语法 | 含义 | 示例 |
|---|---|---|
-L<路径> |
添加库搜索目录 | -L../../output/bin |
-l<库名> |
链接指定库(去掉 lib 前缀和 .lib/.a/.so 后缀) | -lFITKCore |
$$PWD |
qmake 内置变量,当前 .pro 文件所在目录 | $$PWD/../Tools/Win64/SARibbon/lib |
LIBS += |
追加链接参数 | LIBS += -lFITKCore |
🏭 六、CMake (CMakeLists.txt) 中配置库
6.1 📤 创建动态库
1 | # FITK_Kernel/FITKAppFramework/CMakeLists.txt |
💡 为什么导出宏要用
PRIVATE? 因为它只在「编译这个库自己」时才该是导出模式。如果写成PUBLIC,会传染给所有链接它的 exe/库,导致它们也以为自己在「导出」,宏就失效了!
6.2 🎯 创建可执行文件
1 | # StructuralApp/CMakeLists.txt |
6.3 🌉 本项目的跨平台链接宏
1 | # 本项目通用的链接宏,自动处理 Win/Linux 差异 |
6.4 📖 CMake 链接语法总结
| 语法 | 含义 | 示例 |
|---|---|---|
add_library(... SHARED) |
创建动态库 | add_library(FITKAppFramework SHARED) |
add_library(... STATIC) |
创建静态库 | add_library(FITKAppFramework STATIC) |
add_executable(...) |
创建可执行文件 | add_executable(StructuralApp) |
target_link_libraries(A B) |
A 链接到 B | target_link_libraries(StructuralApp FITKAppFramework) |
target_compile_definitions(... PRIVATE XXX) |
编译时定义宏 | target_compile_definitions(FITKAppFramework PRIVATE FITKAppFramework_API) |
find_package(Qt5 ...) |
查找已安装的库 | find_package(Qt5 COMPONENTS Core Widgets ...) |
🚀 七、综合实战:从零搭建一个带库的项目
🎬 场景
我们要做一个数学工具箱:
- 📦 一个动态库
MathTools(提供add()和multiply()) - 🎯 一个可执行文件
MyApp(调用MathTools)
📂 目录结构
1 | MyProject/ |
7.1 1️⃣ 定义导出宏
1 | // MathTools/MathToolsAPI.h |
7.2 2️⃣ 实现库
1 | // MathTools/MathTools.h |
1 | // MathTools/MathTools.cpp |
7.3a 🏭 用 CMake 构建
1 | # 顶层 CMakeLists.txt |
1 | # MathTools/CMakeLists.txt |
1 | # MyApp/CMakeLists.txt |
7.3b 🔧 用 qmake 构建
1 | # MathTools/MathTools.pro |
1 | # MyApp/MyApp.pro |
7.4 4️⃣ 使用库
1 | // MyApp/main.cpp |
7.5 🪟 Windows 下运行注意事项
编译成功后,output/ 目录下会有:
1 | output/ |
运行 MyApp.exe 时,若提示「找不到 MathTools.dll」,说明 exe 找不到 dll。
解决方案(三选一):
- ✅ 把 dll 放到 exe 同目录(最常见、最推荐)
- 🛣️ 把 dll 所在目录加入
PATH环境变量 - ⚠️ 把 dll 丢进系统目录
C:\Windows\System32(不推荐,污染系统)
📌 本项目采用方案 1:所有 dll 统一输出到
output/bin/,exe 也放这里。
7.6 🐧 Linux 下运行注意事项
1 | output/ |
运行前要告诉系统去哪里找 so:
1 | export LD_LIBRARY_PATH=/path/to/output:$LD_LIBRARY_PATH |
或者编译时直接指定 rpath(推荐,免去每次设环境变量):
1 | # 在 CMake 中添加 |
🎓 总结
| 知识点 | 核心要点 |
|---|---|
| 🧱 静态库 | 代码直接嵌入 exe,部署简单,但文件大、更新麻烦(家具焊死在房子里 🏠) |
| 🔌 动态库 | 运行时加载,节省空间、独立更新,但要注意 dll 路径(可插拔的电器 🔋) |
| 🏷️ 导出宏 | Windows 必须用 __declspec(dllexport);Linux 默认导出,无需声明 |
🎚️ FITKAppFrameworkAPI |
本质是 Q_DECL_EXPORT/IMPORT 的别名,靠 #if defined(FITKAppFramework_API) 开关切换 |
| 🔧 qmake 链接 | LIBS += -L<路径> -l<库名> + DEFINES += XXX_API |
| 🏭 CMake 链接 | target_link_libraries(A B) + target_compile_definitions(A PRIVATE XXX_API) |
| 🏃 运行 dll | Windows 放 exe 同级或加 PATH;Linux 用 LD_LIBRARY_PATH 或 rpath |
🧠 记忆口诀
静态焊死,动态可插 🔌
Windows 要报关(导出宏),Linux 全自由 🗽
编译靠.lib,运行靠.dll—— 一个门牌号,一个真房子 🏷️🏠
📝 本文结合 FastCAE-APPStructural 项目实战,用 🎭 生活比喻、📊 对比表格、💻 完整代码,帮你彻底吃透 C++ 动态库与静态库。希望对你有所帮助! 🚀
- 本文作者: 迪丽惹Bug
- 本文链接: https://lyroom.github.io/2026/06/30/C-动态库与静态库详解/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!