3、CMake语法:制作和使用动态库和静态库

动态库和静态库

1 动态库和静态库简介

编译平台:Winsows系统和VS2019,语言:C++

1.1 静态库

在编译时,静态库会被完整地链接到可执行文件中。也就是说,编译器会将静态库中的代码复制到生成的可执行文件里。这使得可执行文件在运行时不再依赖该静态库,即使静态库文件被删除或移动,可执行文件依然能够正常运行。

由于静态库的代码会被完整地复制到可执行文件中,所以会导致可执行文件的体积显著增大。如果多个可执行文件都使用了同一个静态库,那么每个可执行文件中都会包含该静态库的副本,造成磁盘空间的浪费。

每个使用静态库的程序都有一份库代码的副本,这会导致内存中存在大量重复的代码,增加了内存的占用。

如果静态库的代码发生了更新,那么使用该静态库的所有程序都需要重新编译和链接,否则无法使用新的库功能或修复的问题。这在库更新频繁的情况下会带来很大的工作量。

静态链接的可执行文件不依赖外部的库文件,因此可以在没有安装相应静态库的环境中运行,具有较好的独立性。

由于在编译时需要将静态库的代码复制到可执行文件中,所以编译过程相对较慢,尤其是当静态库较大时。

  • 静态库适用场景
    对程序的独立性要求较高,不希望依赖外部环境;或者库的更新频率较低,不需要频繁更新库代码的情况。例如一些嵌入式系统开发,由于运行环境相对固定,使用静态库可以确保程序的稳定性。
    例如,一个大型项目可以将图形处理、网络通信等功能分别封装成不同的静态库。

静态库文件类型

.lib 文件

.lib 文件是静态链接库文件,在 Windows 平台的开发中经常使用。它有两种主要类型:

  1. 静态库(Static Library):包含了实际的代码和数据,当使用静态库进行链接时,链接器会将 .lib 文件中的代码和数据复制到最终的可执行文件中。这种方式使得可执行文件独立于外部库,不依赖于运行时环境中的库文件。例如,你在开发一个 Windows 控制台程序时使用了某个数学计算的静态库,链接后,这个静态库的代码就成为了你可执行文件的一部分。
  2. 导入库(Import Library):用于动态链接库(DLL)的链接过程。导入库并不包含 DLL 的实际代码,而是包含了 DLL 中导出函数和变量的符号信息以及它们的地址。在链接使用 DLL 的程序时,链接器会根据导入库中的信息在可执行文件中记录对 DLL 中函数和变量的引用,当程序运行时,操作系统会根据这些引用信息加载相应的 DLL。
.pdb 文件

.pdb 是程序数据库(Program Database)文件,它是 Visual Studio 等开发工具生成的一种文件,用于存储调试信息。

  1. 调试支持:在调试程序时,调试器(如 Visual Studio 的调试器)会使用 .pdb 文件中的信息来关联源代码和内存中的机器码。这样,当你在调试器中设置断点、查看变量值等操作时,调试器可以准确地定位到源代码中的具体位置,显示变量的名称和值,帮助你快速定位和解决程序中的问题。
  2. 符号信息:.pdb 文件包含了程序中所有符号(如函数名、变量名等)的详细信息,以及它们在内存中的地址和偏移量。这些信息对于调试和分析程序的运行状态非常有用。

.pdb 文件和 .lib 文件是相互独立的。.lib 文件主要用于程序的链接过程,而 .pdb 文件主要用于程序的调试过程。在发布程序时,通常不需要将 .pdb 文件一同发布,因为它主要是为开发和调试阶段服务的;而 .lib 文件(如果是静态库)会成为可执行文件的一部分,或者(如果是导入库)用于引导程序加载动态库。

1.2 动态库

动态库在编译时并不会被链接到可执行文件中,而是在程序运行时才会被加载。可执行文件中仅包含对动态库的引用信息,当程序启动时,操作系统会根据这些引用信息去查找并加载相应的动态库。

动态库的代码不会被复制到可执行文件中,可执行文件只包含对动态库的引用,因此可执行文件的体积相对较小。而且多个程序可以共享同一个动态库的副本,节省了磁盘空间。

多个程序可以共享同一个动态库的实例,在内存中只需要加载一份动态库代码,从而减少了内存的占用。

动态库的更新相对方便,只需要替换动态库文件即可。使用该动态库的程序在下次运行时会自动加载新的库文件,无需重新编译。

动态链接的可执行文件依赖于系统中安装的动态库文件。如果目标系统中缺少相应的动态库,或者动态库的版本不兼容,程序可能无法正常运行。

动态库的编译过程只需要生成库文件,而不需要将库代码复制到可执行文件中,因此编译速度相对较快。

  • 动态库适用场景
    多个程序需要共享相同的功能,或者库的更新比较频繁的情况。例如操作系统中的图形库、网络库等,多个应用程序可以共享这些动态库,同时库的更新也可以方便地推送给所有使用该库的程序。

动态库文件类型

  1. .dll 文件:这是动态库的主要文件,包含了可被多个程序同时使用的代码和数据,在运行时被加载到内存中供程序调用。
  2. .lib 文件:这里的.lib 文件是导入库文件,用于在链接阶段告诉链接器动态库中导出函数和变量的符号信息以及它们的地址,并非静态库中的那种包含实际代码和数据的.lib 文件。
  3. .pdb 文件:程序数据库文件,存储着调试信息,用于在调试过程中帮助开发人员将源代码与运行时的机器码关联起来,方便查看变量值、定位代码位置等。

1.3 总结

通常情况下,发布动态库时,.dll 文件是必须的,而.lib 文件和.pdb 文件是否一同发布,取决于具体需求。如果希望其他开发人员在链接时使用该动态库,就需要提供导入库.lib 文件;.pdb 文件主要用于开发和调试阶段,一般在发布给最终用户的产品中不会包含,但在内部测试或给开发人员使用时可能会提供。

  1. 动态链接库是更推荐的方式,因为它允许更精确的控制和管理链接库的依赖,特别是在大型项目中,它能够避免全局设置可能带来的问题。
  2. 静态库虽然简单,但在复杂的项目中可能会导致意外的问题,通常适用于简单的项目或临时设置。

2. 制作和使用静态库

配搭代码

2.1 CMake

在cmake中,如果要制作静态库,需要使用的命令如下:

bash 复制代码
add_library(库名称 STATIC 源文件1 [源文件2] ...) 

在Linux中,静态库名字分为三部分:lib+库名字+.a,此处只需要指定出库的名字就可以了,另外两部分在生成该文件的时候会自动填充。

在 Windows 系统里,静态库文件的扩展名一般是 .lib。例如,若有一个用于处理数学计算的静态库,可能会被命名为 mathlib.lib 。第三方静态库:如OpenSSL 静态库

  • 实例:有一个目录,需要将src目录中的源文件编译成静态库,然后再使用:
bash 复制代码
.
├── build
├── CMakeLists.txt
├── include           # 头文件目录
│   └── head.h
├── main.cpp          # 用于测试的源文件
└── src               # 源文件目录
    ├── add.cpp
    ├── div.cpp
    ├── mult.cpp
    └── sub.cpp

根据上面的目录结构,可以这样编写CMakeLists.txt文件:

bash 复制代码
cmake_minimum_required(VERSION 3.10)
project(CALC)
include_directories(${PROJECT_SOURCE_DIR}/include)
file(GLOB SRC_LIST "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
add_library(calc STATIC ${SRC_LIST})

这样最终就会生成对应的静态库文件。

生成后用VS对项目进行生成,得到calc.lib文件。

指定输出的路径

由于在Linux下生成的静态库默认不具有可执行权限,所以在指定静态库生成的路径的时候就不能使用EXECUTABLE_OUTPUT_PATH宏了,而应该使用LIBRARY_OUTPUT_PATH,这个宏对应静态库文件和动态库文件都适用。

bash 复制代码
cmake_minimum_required(VERSION 3.0)
project(CALC)
include_directories(${PROJECT_SOURCE_DIR}/include)
file(GLOB SRC_LIST "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
# 设置动态库/静态库生成路径
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
# 生成动态库
#add_library(calc SHARED ${SRC_LIST})
# 生成静态库
add_library(calc STATIC ${SRC_LIST})

2.2 VS

创建步骤:

  1. 创建静态库项目

  2. 添加静态库代码:添加头文件和源文件,不需要main.cpp的执行文件(最好采用namespace来进行区分函数或者类)

  3. 配置静态库项目

  4. 生成静态库:生成->生成解决方案;生成的文件位于配置编译方式的文件夹中

利用第三方库编译静态库

  • 不需要链接器,因为不动态调用库。静态库的使用是将源代码拷贝到项目中进行运行。
    相当于拷贝进入项目一段代码。
    与函数创建静态库相同,不同的是需要在项目属性中配置《包含目录》和《库目录》,不用设置链接器的"输入"。

Note:在编译前要确定第三方库是X86还是64位编译。

2.3 使用静态库

CMake链接静态库

文件目录:

bash 复制代码
$ tree 
.
├── build
├── CMakeLists.txt
├── include
│   └── head.h
├── lib
│   └── libcalc.a     # 制作出的静态库的名字,源文件已经删除,不需要了,生成了静态库
└── src
    └── main.cpp

4 directories, 4 files

在cmake中,链接静态库的命令如下:

bash 复制代码
# 用于设置全局链接库,这些库会链接到之后定义的所有目标上。
link_libraries(<static lib> [<static lib>...])
  • 参数1:指定出要链接的静态库的名字,可以是全名 libxxx.a;也可以是掐头(lib)去尾(.a)之后的名字 xxx
  • 参数2-N:要链接的其它静态库的名字,多个静态库之间用空格隔开

如果该静态库不是系统提供的(自己制作或者使用第三方提供的静态库)可能出现静态库找不到的情况,此时可以将静态库的路径也指定出来:

bash 复制代码
link_directories(<lib path>)  #多个静态库路径之间用空格隔开

CMakeLists.txt文件内容如下:

bash 复制代码
cmake_minimum_required(VERSION 3.0)
project(CALC)
# 搜索指定目录下源文件
file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp) #执行的程序文件
# 包含头文件路径
include_directories(${PROJECT_SOURCE_DIR}/include)
# 包含静态库路径
link_directories(${PROJECT_SOURCE_DIR}/lib)
# 链接静态库
link_libraries(calc)
add_executable(app ${SRC_LIST})

VS链接静态库

  • 方法1

    最直接的方法是将.h和.cpp文件直接放入项目中运行。

  • 方法2

    需要文件:.h头文件和.lib静态库文件

    VS2019中使用步骤:

    1. 添加新项目
    2. 右键项目 → 属性,在其中设置
      1. 配置属性 → VC++目录 → 包含目录:添加:头文件目录
      2. 链接器 → 常规 → 附加库目录:添加:静态库所在位置
      3. 链接器 → 输入 → 附加依赖项:添加:静态库名称
    3. 编写使用代码
    4. 设置项目依赖:右键解决方案 → 项目依赖项,选取相对应的静态库
  • 采用怎样的编译器来生成的静态库,引用的程序就需要用怎样的编译器编译。如果项目中或者静态库采用了第三方库,那么项目必须配置第三方库的信息。

3. 制作和使用动态库

3.1 CMake

在cmake中,如果要制作动态库,需要使用的命令如下:

bash 复制代码
add_library(库名称 SHARED 源文件1 [源文件2] ...) 
  • 在Linux中,动态库名字分为三部分:lib+库名字+.so.
  • 在windows中,动态库的文件后缀为.dll和.lib

根据上面的目录结构,可以这样编写CMakeLists.txt文件:

bash 复制代码
cmake_minimum_required(VERSION 3.0)
project(CALC)
include_directories(${PROJECT_SOURCE_DIR}/include)
file(GLOB SRC_LIST "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
add_library(calc SHARED ${SRC_LIST})

在Linux中,这样最终就会生成对应的动态库文件libcalc.so。在windows中,还需要编译器进一步编译,生成解决方案。

指定输出的路径

对于生成的库文件来说和可执行程序一样都可以指定输出路径。由于在Linux下生成的动态库默认是有执行权限的,所以可以按照生成可执行程序的方式去指定它生成的目录:

bash 复制代码
cmake_minimum_required(VERSION 3.0)
project(CALC)
include_directories(${PROJECT_SOURCE_DIR}/include)
file(GLOB SRC_LIST "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
# 设置动态库生成路径
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
add_library(calc SHARED ${SRC_LIST})
  • 对于这种方式来说,其实就是通过set命令给EXECUTABLE_OUTPUT_PATH宏设置了一个路径,这个路径就是可执行文件生成的路径。

3.2 VS创建

创建步骤:

  1. 选择动态链接库项目进行创建
  2. 添加.h和.cpp文件
  3. 点击项目,然后生成。此时只会生成.dll文件而没有.lib引导文件,需要添加宏
cpp 复制代码
// DLL导出宏
#ifdef XXXLIB_EXPORTS
#define XXX_API __declspec(dllexport)
#else
#define XXX_API __declspec(dllimport)
#endif

函数则在函数前添加 XXX_API,如果时类则在class后和类名前添加XXX_API。

  1. 配置解决平台X86/X64都可以,但是这个导出的DLL,用户引用的时候尽量和导出时的DLL平台一致

  2. 然后点击解决方案,进行生成即可。其中三类文件有用:.h/.dll/.lib。

3.3 使用动态库

在程序编写过程中,除了在项目中引入静态库,好多时候也会使用一些标准的或者第三方提供的一些动态库,关于动态库的制作、使用以及在内存中的加载方式和静态库都是不同的。

CMake

语法

在cmake中链接动态库的命令如下:

bash 复制代码
target_link_libraries(
    <target> 
    <PRIVATE|PUBLIC|INTERFACE> <item>... 
    [<PRIVATE|PUBLIC|INTERFACE> <item>...]...)

用于指定一个目标(如可执行文件或库)在编译时需要链接哪些库。它支持指定库的名称、路径以及链接库的顺序。

bash 复制代码
target_link_libraries(A B C)
target_link_libraries(D A)

动态库的链接具有传递性,如果动态库 A 链接了动态库B、C,动态库D链接了动态库A,此时动态库D相当于也链接了动态库B、C,并可以使用动态库B、C中定义的方法。

  • target:指定要加载的库的文件的名字
    该文件可能是一个源文件
    该文件可能是一个动态库/静态库文件
    该文件可能是一个可执行文件
  • PRIVATE|PUBLIC|INTERFACE:动态库的访问权限,默认为PUBLIC
    如果各个动态库之间没有依赖关系,无需做任何设置,三者没有没有区别,一般无需指定,使用默认的 PUBLIC 即可。
    1. PUBLIC:在public后面的库会被Link到前面的target中,并且里面的符号也会被导出,提供给第三方使用。
    2. PRIVATE:在private后面的库仅被link到前面的target中,并且终结掉,第三方不能感知你调了啥库
    3. INTERFACE:在interface后面引入的库不会被链接到前面的target中,只会导出符号(不清楚函数属于哪个库,)。
应用

动态库的链接和静态库是完全不同的:

  1. 静态库会在生成可执行程序的链接阶段被打包到可执行程序中,所以可执行程序启动,静态库就被加载到内存中了。
  2. 动态库在生成可执行程序的链接阶段不会被打包到可执行程序中,当可执行程序被启动并且调用了动态库中的函数的时候,动态库才会被加载到内存

因此,在cmake中指定要链接的动态库的时候,应该将命令写到生成了可执行文件之后:

bash 复制代码
cmake_minimum_required(VERSION 3.0)
project(TEST)
file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
# 添加并指定最终生成的可执行程序名
add_executable(app ${SRC_LIST})
# 指定可执行程序要链接的动态库名字
target_link_libraries(app pthread)
  • app: 对应的是最终生成的可执行程序的名字
  • pthread:这是可执行程序要加载的动态库,这个库是系统提供的线程库,全名为libpthread.so,在指定的时候一般会掐头(lib)去尾(.so)。
链接第三方动态库

生成了一个动态库,对应的目录结构如下:

bash 复制代码
$ tree 
.
├── build
├── CMakeLists.txt
├── include
│   └── head.h            # 动态库对应的头文件
├── lib
│   └── libcalc.so        # 自己制作的动态库文件
└── main.cpp              # 测试用的源文件

3 directories, 4 files

假设在测试文件main.cpp中既使用了自己制作的动态库libcalc.so又使用了系统提供的线程库,此时CMakeLists.txt文件可以这样写:

bash 复制代码
cmake_minimum_required(VERSION 3.0)
project(TEST)
file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
include_directories(${PROJECT_SOURCE_DIR}/include)
add_executable(app ${SRC_LIST})
target_link_libraries(app pthread calc)
  • 在第六行中,pthread、calc都是可执行程序app要链接的动态库的名字。当可执行程序app生成之后并执行该文件,会提示有如下错误信息:
bash 复制代码
$ ./app 
./app: error while loading shared libraries: libcalc.so: cannot open shared object file: No such file or directory

这是因为可执行程序启动之后,去加载calc这个动态库,但是不知道这个动态库被放到了什么位置,所以就加载失败了。在 CMake 中可以在生成可执行程序之前,通过命令指定出要链接的动态库的位置,指定静态库位置使用的也是这个命令:

bash 复制代码
link_directories(path)

修改之后的CMakeLists.txt文件应该是这样的:

bash 复制代码
cmake_minimum_required(VERSION 3.0)
project(TEST)
file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
# 指定源文件或者动态库对应的头文件路径
include_directories(${PROJECT_SOURCE_DIR}/include)
# 指定要链接的动态库的路径
link_directories(${PROJECT_SOURCE_DIR}/lib)
# 添加并生成一个可执行程序
add_executable(app ${SRC_LIST})
# 指定要链接的动态库
target_link_libraries(app pthread calc)

VS2019

在项目中配置:

右键项目 → 属性

  1. 配置属性 → VC++目录 → 包含目录:头文件位置
  2. 链接器 → 常规 → 附加库目录:lib文件位置
  3. 链接器 → 输入 → 附加依赖项:.lib文件名称

部署动态库:

将.dll复制到以下任一位置:

  1. 项目的运行程序目录下
  2. 系统PATH包含的目录
  3. 与可执行文件相同的目录

设置项目依赖:右键解决方案 → 项目依赖项。

生成解决方案并运行

相关推荐
兜小糖的小秃毛5 分钟前
文号验证-同时对两个输入框验证
开发语言·前端·javascript
anqi2731 分钟前
如何在 IntelliJ IDEA 中编写 Speak 程序
java·大数据·开发语言·spark·intellij-idea
XuX0335 分钟前
MATLAB小试牛刀系列(1)
开发语言·matlab
Suckerbin1 小时前
第十四章-PHP与HTTP协议
开发语言·http·php
Best_Liu~1 小时前
TransactionTemplate 与@Transactional 注解的使用
java·开发语言·spring boot·后端
谈不譚网安1 小时前
初识Python
开发语言·python
慕雪华年1 小时前
【Python】使用uv管理python虚拟环境
开发语言·python·ai·uv·mcp
狗蛋儿l2 小时前
qt 3d航迹图
开发语言·qt·3d
学习机器不会机器学习2 小时前
深入浅出JavaScript常见设计模式:从原理到实战(2)
开发语言·javascript·设计模式
阿方.9182 小时前
C语言----操作符详解(万字详解)
c语言·开发语言