关于CMake系列知识点来说,第三方库的运用是一个绕不开的知识点,废话不多说,直接开讲。

在此之前仍是必须先简单了解一下基本知识点:库能够分为静态库动态库

  • 静态库:静态库在程序编译链接时,将库中用到的代码直接链接(或许说复制)到终究的可执行文件中。这意味着,一旦你的程序链接了静态库,那么即使在没有库文件的体系上,你的程序也能正常运转,由于它现已包含了一切需求的代码。然而,这也会导致你的可执行文件比链接动态库的版别大,由于它包含了一切的库代码。

一般的文件后缀:

Windows Linux和Android macOS和iOS
*.lib *.a *.a或*.framework
  • 动态库:与静态库不同,动态库在程序编译链接时,并不会被复制到终究的可执行文件中。相反,当程序运转时,它会从体系中加载动态库。这意味着,假如你的程序链接了动态库,那么在运转程序的体系上,需求有一个相应的动态库文件。动态库的优点是它能够被多个程序共享,这能够削减磁盘空间和内存的运用。此外,假如动态库更新了,程序能够在不重新编译的情况下运用新版别的库。

一般的文件后缀:

Windows Linux和Android macOS和iOS
*.dll *.so *.dylib或*.framework

PS: 这儿再多说几句,自己编写代码在生成动态库的时分会生成3个文件:*.dll*.lib*.exp。当你想把你的程序链接到这个DLL上时,你必须要把这个LIB文件随DLL文件也拷贝过去,由于这个LIB文件是包含指向DLL中函数和变量的引用(便是这个代码生成它的:__declspec(dllexport),假如你的代码里没有这个自然也不会生成这个LIB文件),链接时分用的,当链接完成后你在分发时又能够不用这个LIB文件。这个导入库LIB文件与静态库LIB文件虽然是相同的后缀名,可是它们的意义彻底不同。至于什么EXP文件与DEF文件这些你能够不用管它,它们都是中心产物。


【注:猴急的人能够越过这一部分,太长了。】在了解了什么是静态库什么是动态库之后,我们还需求了解另外一个东西:C++运转时库,由于我自身是做Android应用层开发的,所以这儿也能够提一提或许我们看到过的一个东西:libc++_shared.so。给我们上个图方便回想一下,比方微信的apk里就有它:

CMake系列:动态/静态库的介绍以及导入
这个库便是C++在Android上包含了C++ 标准库的完成(包含IO操作、字符串处理、数学计算等功能),以及一些底层的服务,如内存管理和线程处理。此外,C++运转时库还包含了运转C++程序所必需的启动和退出代码。例如,在程序启动时,C++运转时库会负责初始化大局和静态变量,在程序退出时,C++运转时库会负责清理资源。。在Android里,它是由NDK供给的,详细位置如下图:

CMake系列:动态/静态库的介绍以及导入
当然,它还有另一种方法—静态库,位置也来张图:

CMake系列:动态/静态库的介绍以及导入
至于用哪个能够依据需求来调整。

或许看完前面的介绍现在仍是有一些人不能理解这个运转时库到底是什么?那我能够提一下Java Runtime Environment(JRE),你现在大概知道它是什么了吧?你没看错,C++也是需求运转时库的,仅仅说这个运转时库不会很大,有的是直接静态导入到了exe里,所以你或许找不到它。

所以,在链接第三方库时(在这儿体系库也算是第三方库),能够用静态或动态的方法来链接这个C++运转时库。比方:你在Windows上用MSVC来编译链接你的库时,你在cmake里能够设置它是用静态仍是动态的方法来链接:

#设置为静态链接运转时库
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")

或许

#设置为动态链接运转时库
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreadedDLL$<$<CONFIG:Debug>:Debug>")

其实,MSVC是经过这几个指令行选项 /MT/MTd/MD/MDd来控制的,在cmake的这些设置都是一一对应罢了:

  • 静态多线程:MultiThreaded 对应于 /MT
  • 静态多线程(带调试):MultiThreadedDebug 对应于 /MTd
  • 动态多线程:MultiThreadedDLL 对应于 /MD
  • 动态多线程(带调试):MultiThreadedDebugDLL 对应于 /MDd

当然,你什么也不设置,MSVC的默许行为是/MD/MDd,即MultiThreadedDLLMultiThreadedDebugDLL

额。。。假如是其他编译器呢?

  • 我们先来看静态链接运转时库:

GCC(包含MinGW):

target_link_options(myprogram PRIVATE -static-libstdc++ -static-libgcc)
#或许要想全部运用静态链接的话(但并不引荐,乃至或许有些操作体系会报错):
target_link_options(myprogram PRIVATE -static)

Clang:

target_link_options(myprogram PRIVATE -static-libc++ -static-libc++abi)

那假如是Android渠道呢?

#能够,但不引荐,由于是大局的设置,乃至或许报错。
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static-libc++ -static-libc++abi")
#或许用android渠道更简单的方法
set(ANDROID_STL c++_static)

假如是JNI的话也能够直接在gradle里这样装备:

android {
    defaultConfig {
        externalNativeBuild {
            cmake {
                arguments "-DANDROID_STL=c++_static"
            }
        }
    }
    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }
}
  • 再说动态链接运转时库:

由于默许情况下,一切渠道都是默许的动态链接运转时库,所以只要你不明确的指定是静态链接运转时库,那么它就一定是动态链接运转时库。

那假如我是一个“铁脑壳”,非要指定呢?

MSVC:

set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreadedDLL$<$<CONFIG:Debug>:DebugDLL>")

GCC和Clang:

target_link_options(your_target PRIVATE -shared-libgcc -shared-libstdc++)

Android:

#不引荐:
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -shared-libgcc -shared-libstdc++")
#或
#引荐:
set(ANDROID_STL c++_shared)

假如是JNI的话也能够直接在gradle里这样装备:

android {
    defaultConfig {
        externalNativeBuild {
            cmake {
                arguments "-DANDROID_STL=c++_shared"
            }
        }
    }
    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }
}

既然是这样,那这个C++运转时库在哪?分发的时分需求我手动添加吗?

  • 在Android中,当你挑选了动态链接运转时库时,libc++_shared.so会主动包含在你的APK中。
  • 关于Windows的话,它其实是一个名叫msvcp140.dll的文件,这玩意儿应该由Microsoft Visual C++ Redistributable装置的,我也没细心考究它是否会在默许的Windows中存在。
  • 关于Linux,不同的发行版如同还不相同,不过都包含了这个C++运转时库,详细的情况需求再研究。
  • 关于macOS和iOS,体系也自带了,这个库如同是:libc++.1.dylib,Apple的Clang编译器,它会主动动态链接到它上面。

好,现在到这儿解释了动态库/静态库与运转时库,我们现在能够自由装备链接它们了,那CMake里有哪些链接方法呢?

  1. 运用find_package指令: 关于一些常用库,CMake供给了对应的Find<PackageName>.cmake模块或<PackageName>Config.cmake模块,能够运用find_package指令主动找到这些库并创立对应的导入方针。例如:
#假如你想静态链接就加上这句代码,不然便是动态链接。每个库的变量不相同,请自己查找。
set(OPENSSL_USE_STATIC_LIBS TRUE)
find_package(OpenSSL REQUIRED)
target_link_libraries(MyExecutable PRIVATE OpenSSL::SSL)

在这个比如中,OpenSSL::SSL便是一个导入方针,它包含了链接OpenSSL库所需的一切信息,包含库的途径、头文件的途径以及其他编译选项。

需求留意的是,并不是一切库都供给了CMake的查找模块,也不是一切查找模块都供给了挑选静态链接或动态链接的选项。

  1. 直接指定库文件的途径: 你能够手动找到库文件的途径,然后在target_link_libraries指令中直接运用这个途径。例如:
#静态
target_link_libraries(MyExecutable PRIVATE "/path/to/mylibrary.lib")
#动态,它们之间的区别便是文件自身。
target_link_libraries(MyExecutable PRIVATE "/path/to/mylibrary.so")

在这个比如中,"/path/to/mylibrary.lib"应该替换为你的库文件的实际途径。这种方法能够用于链接任何类型的库,只需求供给正确的库文件途径即可。

  1. 创立导入方针:你能够手动创立一个导入方针,然后在这个导入方针上设置库文件的途径以及其他特点。例如:
# 动态链接
add_library(MyLibrary SHARED IMPORTED)
set_target_properties(MyLibrary PROPERTIES
    IMPORTED_LOCATION "/path/to/mylibrary.dll"  # Windows
    # 或许
    # IMPORTED_LOCATION "/path/to/mylibrary.so"  # Linux
    INTERFACE_INCLUDE_DIRECTORIES "/path/to/mylibrary/headers"
)
# 静态链接
add_library(MyLibrary STATIC IMPORTED)
set_target_properties(MyLibrary PROPERTIES
    IMPORTED_LOCATION "/path/to/mylibrary.lib"  # Windows
    # 或许
    # IMPORTED_LOCATION "/path/to/mylibrary.a"  # Linux
    INTERFACE_INCLUDE_DIRECTORIES "/path/to/mylibrary/headers"
)
target_link_libraries(MyExecutable PRIVATE MyLibrary)

在这个比如中,MyLibrary是你创立的一个导入方针,"/path/to/mylibrary.*""/path/to/mylibrary/headers"应该替换为你的库文件和头文件的实际途径。 至于add_library(MyLibrary SHARED IMPORTED)里面的是SHARED仍是STATIC并不能决定是静态仍是动态,实质仍是看详细的库文件。仅仅说加上这个,编译器会预处理一些东西,原则上是与实在的库文件保持相同就行。假如不相同,也有或许会编译经过,要看详细代码。

  1. 自定义查找模块: 假如CMake没有供给查找某个库的模块,或许供给的模块不能满足你的需求,你能够编写自定义的查找模块。自定义的查找模块一般会运用CMake供给的find_pathfind_libraryfind_package_handle_standard_args等指令来找到库文件和头文件的途径,并创立导入方针或许设置变量。至于详细怎么写,请参阅其他文章。

如同讲到这儿还没说,静态库和动态库在链接时,为什么需求跟C++运转时库发生点关系?由于假如你链接的库是运用静态运转时库编译的,那么你自己的库用动态运转时的话就会报:error LNK2038过错(MSVC里是这个错,其他的自己试)。所以,自己的库要与链接的库的C++运转时库要相同。

暂时就这么多,本来还想多写点,发现有其他事要做了。。。