CMake如何编译成库?
- 为什么要有库?
库就是预先写好,编译好的代码集合。存在的意义,1)避免重复造轮子和重复编译,库是提前编译好的二进制文件 ,你的代码在编译时直接链接它,不需要把库的源码重新编译一遍,大大节省了编译时间。;2)保护源码(知识产权) :编译成库让别人可以直接调用我的函数,但看不到cpp源码,看不到里面的逻辑。3)模块化与复用:大项目可以拆分给不同的团队开发不同的库,最后拼在一起。
静态库:就是在程序编译和链接阶段,把库里被用到的代码部分,像打包裹一样,直接复制粘贴到最终的可执行文件(如 .exe 或 Linux 下的无后缀文件)里面。 静态库的后缀.lib(windows中),.a(Linux中) Archive。
- 静态库本身 :是库的作者 把所有功能都编译好,打包成的一个**"大仓库"** 。它包含多余的代码。
动态库:又叫共享库,在编译链接阶段,程序只记录一个"依赖清单" ,并不复制代码。等到程序真正运行的时候,操作系统才会去把动态库加载到内存里,让大家一起用。
-
Windows 下后缀 :
.dll(Dynamic Link Library) -
Linux 下后缀 :
.so(Shared Object) -
比喻 :你买了一个乐高说明书,需要玩的时候,去公共游乐场借公用的积木来拼。
特点:
-
节省空间:十个程序用同一个动态库,内存里只有一份代码,大家共享。
-
更新方便 :如果库出了新版本(且接口没变),你只要把电脑里的
.dll或.so文件替换掉,程序不需要重新编译,下次运行自动就用新的。 -
依赖环境:如果你把程序发给别人,别人电脑上没有这个动态库,程序就会报错打不开(常见的"缺少 xxx.dll"报错)。
便于后期迭代更新,且如果运行的时候没运行相关的接口程序,就不会占用内存。
-
省资源:多个程序(或同一程序的多个进程)在内存里共享同一份底层代码(如各种系统 DLL)。(更多的是用系统中原本就有的一些dll而不是程序下载的时候带来的)
-
好更新:改个 bug 只替换小 DLL,不用重装整个软件(你的理解)。
-
易扩展:支持插件化架构(比如浏览器加个插件、Photoshop 滤镜),主程序根本不需要重新编译。
| 对比维度 | 静态库(.a / .lib) | 动态库(.so / .dll) |
|---|---|---|
| 链接时机 | 编译时(打包进程序) | 运行时(操作系统动态加载) |
| 可执行文件体积 | 大(包含了库的代码) | 小(只记录了依赖) |
| 运行速度 | 略快(没有加载开销) | 略慢一点点(启动时需加载) |
| 内存/磁盘占用 | 浪费(多个程序各自存一份) | 节省(多程序共享一份) |
| 库更新 | 必须重新编译整个程序 | 直接替换库文件即可(接口不变) |
| 程序发布 | 简单(一个文件走天下) | 麻烦(需附带一堆 .dll / .so) |
***********
add_executable(lesson1_1 main.cpp ../lesson1_2/add.cpp) 这是执行的核心,这是添加要编译的文件。
include_directories(../lesson1_1) 添加头文件中包含的路径
add_subdirectory(lesson1_1) 在外层CMakelists中添加要编译的子文件夹
关于动态库
在cpp文件的函数前面加 __declspec(dllexport)
cpp
__declspec(dllexport) int add(int a, int b) {
return a + b;
}
++一份 .dll 的朋友圈里一定有一个同名 .lib(导入库)------ 但链接时用 .lib,运行时用 .dll,而源码树里既不该有它们,也不该手工维护它们。++
要运行含有dll的exe文件,那么相应的dll文件,要在同级目录下,或者写在系统环境变量中
target_link_libraries和target_include_directories的作用区别:
-
target_include_directories:解决**"去哪找头文件(.h / .hpp)"**的问题(编译阶段)。 -
target_link_libraries:解决**"去哪找库文件(.a / .so / .lib)以及怎么把它们拼起来"**的问题(链接阶段)。 (也就是最终把多个.a 链接起来,最终生成可执行文件的过程,但不是建立 依赖关系,即 并不是 1.a中要调用2.a中的函数,就把1.a和2.a链接起来,不是这样的。 如果是1.a中要用到2.a中的函数,只能是在 都还是.o文件的时候通过add_library(add STATIC add.cpp $<TARGET_OBJECTS:common_object>) 这种方式建立连接,生成一个1.a)
也就是说,target_link_libraries 只能是把多个并列功能的.a(.lib)文件打包成可执行文件,而不能把前后依赖关系,打包成.a,也不能把两个.a文件进行合并。
如果是要链接前后依赖关系的库的话,应该用add_library
.a 相当于.lib,
.so相当于.dll
# 告诉编译器,编译 add.cpp 的时候,去 当前 文件夹下找头文件
# 因为是 PRIVATE,如果别人链接了 add,他们得不到这个 include 路径(对外不公开)。 如果是PUBLIC就是如果A依赖了add这个,那么add能找到的头文件,A也能找到并使用(即对外公开)。
target_include_directories(add PRIVATE ./)
add_library 到底生成什么文件?完全取决于你给 add_library 传的第二个参数(类型)
| 类型参数 | 生成的文件 (Linux/macOS) | 生成的文件 (Windows) | 作用与特点 |
|---|---|---|---|
OBJECT |
.o 文件集合 |
.obj 文件集合 |
只编译,不打包。是最基础的"零件"。 |
STATIC |
.a 静态库 |
.lib 静态库 |
把 .o 文件打包成一个压缩包。程序运行时不需要它。 |
SHARED |
.so / .dylib 动态库 |
.dll (+ .lib 导入库) |
动态链接库,程序运行时需要加载。 |
MODULE |
.so |
.dll |
插件库,通常用 dlopen 动态加载,不直接链接。 |
INTERFACE |
不生成任何文件 | 不生成任何文件 | 纯头文件库(Header-only),只用来传递编译属性(如 include 路径)。 |
ALIAS |
不生成任何文件 | 不生成任何文件 | 给已有的 Target 起个别名(如 add_library(ns::my_lib ALIAS my_lib)) |
-
.o是编译的产物(永远在后台默默发生)。 -
.a是打包(归档) 的产物(STATIC产生)。 -
target_link_libraries是给最终链接(缝合) 阶段下达的指令,它绝对不会去修改现有的.a或.o文件本身。
vscode中没有相关显示cmkelist。
运行 CMake: Configure :这是最关键的一步。按 Ctrl+Shift+P,输入并选择 CMake: Configure。这个命令会强制 CMake Tools 加载项目并开始配置,配置完成后,状态栏的按钮通常就会出现。