引言
上一篇文章 【开启】 我们从零开始搭建起来了一个简单的 hello world 程序。并在文章的结尾,我们写了一个工具类,用于相加两个数,那么本章就围绕这个工具类展开。
你也可以封装库
我们回顾一下项目结构:
1 | test |
我们直接把 utils.c 同 main.c 打包到了一起,如果我们想把 utils 打包成库,供其他程序使用,应该怎么做呢?
静态库
由于我们要单独打库,所以我们需要修改一下我们的编译规则:
1 | cmake_minimum_required(VERSION 3.21) |
我们分析一下以上的修改:
- 6 行,我们使用 set 命令取代之前的
aux_source_directory()
,因为我们这次需要把 main 函数跟库分开打。这里 set 指定将 main.c 保存到了 MAIN_SOURCE 变量下,后续只要${MAIN_SOURCE}
即可获取,另外注意到 PROJECT_SOURCE_DIR 这个变量,该变量则代表了当前项目的根路径。 - 10 行,我们以同样的方式指定了库的源码程序,并存到了 UTILS_SOURCE 变量中。
- 14 行,我们使用
add_library
来告诉编译器,我们要生成库,名字是 utils,生成为静态库,参与编译的源码为 UTILS_SOURCE 指定的。当然,static 关键字可以不指定,默认情况下生成的也是静态库。 - 15 行,我们把 test 执行文件注释掉,因为它需要静态库,但是此时还没有编译出来,如果不注释掉,当编译的时候会报以下错误:
1 | Undefined symbols for architecture x86_64: |
不过不要紧,后续我们再解决这个问题。
- 执行以下命令进行编译:
1 | cmake -S . -B build |
那么在 build 路径下就生成了 libutils.a 这个文件,也就是我们需要的静态库文件。
使用静态库
有了静态库文件之后,我们怎么使用它呢?
1 | cmake_minimum_required(VERSION 3.21) |
我们着重看一下 14 行以及 18 行。其中 14 行告诉编译器,应该去哪里找库文件,UTILS_LIB 用于存放库信息,NAMES 跟 PATHS 分别代表了库的名字以及查找路径,注意我们生成的库都以 lib + 源码文件的名字开头,如本例中 utils 编译出来的库叫做 libutils.a 。但是我们使用 find_library 查找时,名字可以不用加 lib。
找到库之后,我们需要将该库链接到我们的目标程序,这也是 target_link_libraries 所做的工作。该命令则是告诉编译器将 ${UTILS_LIB} 库链接到 test 这个目标程序。
接下来就可以启动或者编译我们的源程序了!
📚 Tips
target_link_libraries 必须要放到 add_executable 指令的下边,也就是说,必须要先生成目标文件,才可以为目标文件创建链接属性。除了 target_link_libraries 命令外,
link_libraries(${UTILS_LIB})
也可以链接,如果我们的库需要全局链接,那么可以使用该命令(并且只有放到根路径中的 CMakeLists.txt 才会生效),否则还是建议使用 target_link_libraries。除此之外还有另外一个不再推荐的指令 target_link_libraries() 。
动态库
创建好静态库之后,我们这次创建动态库,方法很简单,只将 add_library(utils STATIC ${UTILS_SOURCE})
修改为 add_library(utils SHARED ${UTILS_SOURCE})
即可。
删除 build 路径,重新编译,当动态库创建好之后,启动我们的项目,提示:
1 | make[3]: *** No rule to make target `../build/libutils.a', needed by `test'. Stop. |
奇怪了,我们生成的动态库明明是 libutils.dylib,为什么又去找 libutils.a 了呢?我们回想一下,谁来保存我们的库路径?当然是 find_library(UTILS_LIB NAMES utils PATHS ${PROJECT_SOURCE_DIR}/build)
这个指令中的 UTILS_LIB 变量。既然我们生成了新的库,但是它还去找老的静态库,也就是说,这个变量被缓存了。那么怎么解决这个问题呢?很简单,只需要清掉这个缓存就可以了,我们在 find_library(UTILS_LIB NAMES utils PATHS ${PROJECT_SOURCE_DIR}/build)
上添加另外一条指令 unset(UTILS_LIB CACHE)
,再启动我们的程序,发现可以正常运行了。