在众多成熟的项目中,有时会遇到这样的情况:某个模块是整个项目核心技术的核心,我们希望将其保密,不对外泄露。为了解决这个问题,我们可以将这个模块创建为静态库或动态库。这样,库文件就能将模块的实现细节隐藏起来,只暴露必要的接口。这样一来,其他程序只需调用库中的接口,无需了解具体的实现细节,从而提高了代码的安全性和可维护性。
此外,如果一个模块被多个程序所使用,我们可以将其编译成库文件,实现代码的复用。多个程序可以共享同一个库文件,避免了重复编写和维护相同的代码。这大大降低了开发成本,提高了开发效率。
库(Library)是一组可重用的代码,提供一些函数和数据结构,以便其他程序在其基础上开发功能。库分为静态库和动态库两种类型。下面我们将详细介绍这两种库的区别。
在编译程序时,需要将库文件链接到程序中,以便程序可以使用库中的函数和变量。静态库(Static ?Library)是在编译时与应用程序代码一起链接生成的。当程序需要使用静态库中的函数或变量时,链接器会将静态库的代码复制到最终的可执行文件中,因此在运行时不需要额外的库文件。
与静态库相比,动态库(Dynamic ?Library)在运行时才被加载。它和应用程序代码是分开编译和链接的。当需要使用动态库中的函数或变量时,操作系统会将动态库文件加载到内存中,并在需要的时候将其中的代码映射到应用程序的地址空间中,从而扩充应用程序的功能。
在CMake中,我们能够利用add_library指令来创建静态库或动态库。创建静态库的命令格式如下:
add_library(name ?STATIC ?库源文件列表)
在上述命令中,第一个参数是库的名称,第二个参数STATIC表示创建静态库,SHARED表示创建动态库。第三个参数是库的源文件列表。一旦库文件被创建,我们就可以在程序中使用库中的函数和变量了。
比如说,如果我们想把显示模块编译成静态库,可以遵循以下步骤:首先将main函数移除src目录,因为我们在制作静态库时并不需要这个文件。为了在add_library命令中简洁地指定库的源文件列表,我们可以定义一个变量SRC_PATH,其值为源文件路径。
set(SRC_PATH ?${PROJECT_SOURCE_DIR}/src)
接下来,我们对这个目录下的lcd.cpp文件进行相应的操作。为了给静态库命名,在Linux中,静态库文件的后缀为“.a”,并且静态库名字分为三部分:lib+库名字+.a。我们只需指定出库的名字即可,其余两部分在生成该文件时会自动填充。这里我们取名为lcd。
add_library(lcd STATIC ${SRC_PATH}/lcd.cpp)
完成静态库的制作命令后,我们需要注释掉生成执行文件的命令,因为我们这里并不需要生成可执行文件。如果不注释掉这个命令,可能会引发编译错误。这一点我们将在后面详细解释。
然后我们还是重新编译测试看看,是否这样做真的可以做到生成我们所需要的静态库
我们已经成功地构建了静态库。如果我们需要的是动态库,在Linux系统中,其文件后缀为“.so”。创建动态库同样需要使用add_library命令,但第二个参数应改为SHARED,以表示创建的是动态库(也称为共享库)。
如果我们想把键盘模块构建为动态库,我们可以将其命名为key作为第一个参数,将第二个参数改为SHARED,并指定键盘模块的源文件路径作为第三个参数。以下是如何做到这一点的命令:
add_library(key SHARED ${SRC_PATH}/key.cpp)
修改CMakeLists.txt文件添加制作动态库语句后,我们再重新编译整个项目。
那么现在这两个库也已经全部生成了。
在构建一个项目时,库文件的组织和管理是非常重要的。为了提高可维护性和清晰度,通常建议将库文件放在一个独立的目录中。这个目录可以命名为“lib”或者使用其他名称,关键是保持一致性。通过将库文件放在单独的目录中,可以更好地组织项目结构,并使得其他开发者更容易找到和使用这些库文件。
可以看到我们在build目录中编译,那库文件自然就默认生成在这个目录下,那我们该如何让这个库文件直接自动生成到我们创建的lib目录下呢?
在CMake中,可以使用LIBRARY_OUTPUT_PATH变量来指定编译生成的库文件应该保存的路径。通过设置这个变量,可以控制库文件的输出位置。在CMakeLists.txt文件中,可以按照以下方式进行设置:
set(LIBRARY_OUTPUT_PATH <path>)
其中,<path>是要设置的输出路径。需要注意的是,这个语句必须在add_library语句之前调用,以确保对所有库的输出路径进行正确设置。
在我们这里可以这样设置:
set(LIBRARY_OUTPUT_PATH ?${PROJECT_SOURCE_DIR}/lib)
修改完后,我们继续重新构建并编译整个项目,这时发现我们生成的库文件已经保存到我们指定的目录lib底下了。
在库文件生成之后,我们需要将其与程序进行链接,这样才能使程序调用库中的函数和利用库中的变量。在cmake环境中,无论是链接静态库还是动态库,都可以采用target_link_libraries命令来实现。该命令的语法格式如下:
target_link_libraries(<target> [PRIVATE|PUBLIC|INTERFACE] <library> )
在这里,<target>代表需要链接的目标项目或库的名称,而<library>则是需要链接的库的名称。通过向该命令添加多个库文件,我们可以将它们合并到目标项目中。此外,我们还可以使用私有(PRIVATE)、公共(PUBLIC)或接口(INTERFACE)关键字来定义链接库的属性。
-私有(PRIVATE):这意味着链接库仅对目标项目本身可用。在这种情况下,其他目标项目无法使用该库。
-公共(PUBLIC):表示链接库对目标项目及其所使用的其他项目均可用。这意味着其他项目可以调用该库。
-接口(INTERFACE):这意味着链接库仅对需要使用该目标及其所使用的其他项目可用。这表明其他项目可以调用该库,但该库不会直接用于目标项目本身。
这种分类有助于管理库的依赖关系,确保项目在编译和运行时能够正确地访问所需的库函数和变量。在实际开发过程中,根据项目需求和库的特性选择合适的链接方式,可以提高代码的可维护性和可扩展性。
在本项目当中,我们如何运用 ?`target_link_libraries`命令来实现静态库与动态库的链接呢?在构建库文件的过程中,为了确保库文件的顺利生成,我曾注释掉了 ?`add_executable`这条命令,该命令用于创建可执行文件。这是因为我在 ?`main`函数中调用了库文件的函数,若没有链接库文件的语句,将导致编译无法通过。现在,我们将注释取消,添加可执行文件所依赖的源文件,暂不链接库文件,重新构建并编译以观察效果。
可见确实是我们预想的那样,那么接下来我们开始链接库文件,通过target_link_libraries命令链接lcd ,key两个库文件到可执行文件。
target_link_libraries(${CMAKE_PROJECT_NAME} ?PRIVATE??lcd??key)
在此需要注意的是,通常情况下,我们会将链接库文件的命令置于创建可执行文件的命令之后。这是因为当链接动态库时,动态库在生成可执行程序的链接阶段并不会被集成到可执行程序中。只有当可执行程序启动并调用动态库中的函数时,动态库才会被加载到内存。因此,在CMake中指定要链接的动态库时,应将相关命令置于生成可执行文件之后。
若链接库的语句出现在可执行文件的创建语句之前,将会导致编译无法通过。因此,我们通常会将`add_executable`命令放在`target_link_libraries`命令之前,以确保可执行文件能够正确链接库文件并运行。
可以看一下,如果放在add_executable是个什么结果:
因此我们需要这样写CMakeLists.txt:
修改完后,再重新构建并编译整个项目,可见已经可以编译成可执行文件了。
但在构建链接库文件时,我们需关注一个重要细节:若所链接的静态库或动态库并非系统自带,而是自行制作或采用第三方提供的库,在可执行程序启动过程中,可能出现库找不到的情况,即无法确定库所在位置。因此,我们需要指定库的路径。在 ?CMake ?中,可在生成可执行程序前,利用命令 ?`link_directories`指明要链接的静态库位置,同样可用于指定动态库位置。该命令语法如下:
link_directories(<lib path>)
接下来,我们需在 ?CMakeLists.txt文件中添加相应内容,以指定库的路径。