<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Android NDK 開發(fā)之 CMake 必知必會

          共 10269字,需瀏覽 21分鐘

           ·

          2021-09-16 14:48

          Android Studio 從 2.2 版本起開始支持 CMake ,可以通過 CMake 和 NDK 將 C/C++ 代碼編譯成底層的庫,然后再配合 Gradle 的編譯將庫打包到 APK 中。

          這意味就不需要再編寫 .mk 文件來編譯 so 動態(tài)庫了。

          CMake 是一個跨平臺構(gòu)建系統(tǒng),在 Android Studio 引入 CMake 之前,它就已經(jīng)被廣泛運用了。

          Google 官方網(wǎng)站上有對 CMake 的使用示范,可以參考 官方指南。

          總結(jié)官網(wǎng)對 CMake 的使用,其實也就如下的步驟:

          1. add_library 指定要編譯的庫,并將所有的 .c.cpp 文件包含指定。

          2. include_directories 將頭文件添加到搜索路徑中

          3. set_target_properties 設(shè)置庫的一些屬性

          4. target_link_libraries 將庫與其他庫相關(guān)聯(lián)

          如果你對上面的步驟還是不了解,那么接下來就更深入了解 CMake 相關(guān)內(nèi)容吧~~~

          CMake 的基本操作

          以 Clion 作為工具來講解 CMake 的基本使用。

          clion_cmake_build

          CMake 編譯可執(zhí)行文件

          一個打印 hello world 的 cpp 文件,通過 CMake 將它編譯成可執(zhí)行文件。

          在 cpp 的同一目錄下創(chuàng)建 CMakeLists.txt 文件,內(nèi)容如下:

          1# 指定 CMake 使用版本
          2cmake_minimum_required(VERSION 3.9)
          3# 工程名
          4project(HelloCMake)
          5# 編譯可執(zhí)行文件
          6add_executable(HelloCMake main.cpp )

          其中,通過 cmake_minimum_required 方法指定 CMake 使用版本,通過 project 指定工程名。

          add_executable 就是指定最后編譯的可執(zhí)行文件名稱和需要編譯的 cpp 文件,如果工程很大,有多個 cpp 文件,那么都要把它們添加進來。

          定義了 CMake 文件之后,就可以開始編譯構(gòu)建了。

          CMake 在構(gòu)建工程時會生成許多臨時文件,避免讓這些臨時文件污染代碼,一般會把它們放到一個單獨的目錄中。

          操作步驟如下:

          1# 在 cpp 目錄下創(chuàng)建 build 目錄
          2mkdir build
          3# 調(diào)用 cmake 命令生成 makefile 文件
          4cmake ..
          5# 編譯
          6make

          在 build 目錄中可以找到最終生成的可執(zhí)行文件。

          這就是 CMake 的一個簡單操作,將 cpp 編譯成可執(zhí)行文件,但在 Android 中,大多數(shù)場景都是把 cpp 編譯成庫文件。

          CMake 編譯靜態(tài)庫和動態(tài)庫

          同樣還是一個 cpp 文件和一個 CMake 文件,cpp 文件內(nèi)容為打印字符串的函數(shù):

          1#include <iostream>
          2void print() {
          3    std::cout << "hello lib" << std::endl;
          4}

          同時,CMake 文件也要做相應(yīng)更改:

          1cmake_minimum_required(VERSION 3.12)
          2# 指定編譯的庫和文件,SHARED 編譯動態(tài)庫
          3add_library(share_lib SHARED lib.cpp)
          4# STATIC 編譯靜態(tài)庫
          5# add_library(share_lib STATIC lib.cpp)

          通過 add_library 指定要編譯的庫的名稱,以及動態(tài)庫還是靜態(tài)庫,還有要編譯的文件。

          最后同樣地執(zhí)行構(gòu)建,在 build 目錄下可以看到生成的庫文件。

          到這里,就基本可以使用 CMake 來構(gòu)建 C/C++ 工程了。

          CMake 基本語法

          熟悉了上面的基本操作之后,就必然會遇到以下的問題了:

          • 如果要參與編譯的 C/C++ 文件很多,難道每個都要手動添加嘛?

          • 可以把編譯好的可執(zhí)行文件或者庫自動放到指定位置嘛?

          • 可以把編譯好的庫指定版本號嘛?

          帶著這些問題,還是要繼續(xù)深入學(xué)習(xí) CMake 的相關(guān)語法,最好的學(xué)習(xí)材料就是 官網(wǎng)文檔 了。

          為了避免直接看官方文檔時一頭霧水,這里列舉一些常用的語法命令。

          注釋與大小寫

          在前面就已經(jīng)用到了 CMake 注釋了,每一行的開頭 # 代表注釋。

          另外,CMake 的所有語法指令是不區(qū)分大小寫的。

          變量定義與消息打印

          通過 set 來定義變量:

          1# 變量名為 var,值為 hello
          2set(var hello) 

          當(dāng)需要引用變量時,在變量名外面加上 ${} 符合來引用變量。

          1# 引用 var 變量
          2${var}

          還可以通過 message 在命令行中輸出打印內(nèi)容。

          1set(var hello) 
          2message(${var})

          數(shù)學(xué)和字符串操作

          數(shù)學(xué)操作

          CMake 中通過 math 來實現(xiàn)數(shù)學(xué)操作。

          1# math 使用,EXPR 為大小
          2math(EXPR <output-variable> <math-expression>)
          1math(EXPR var "1+1")
          2# 輸出結(jié)果為 2
          3message(${var})

          math 支持 +, -, *, /, %, |, &, ^, ~, <<, >> 等操作,和 C 語言中大致相同。

          字符串操作

          CMake 通過 string 來實現(xiàn)字符串的操作,這波操作有很多,包括將字符串全部大寫、全部小寫、求字符串長度、查找與替換等操作。

          具體查看 官方文檔。

           1set(var "this is  string")
          2set(sub "this")
          3set(sub1 "that")
          4# 字符串的查找,結(jié)果保存在 result 變量中
          5string(FIND ${var} ${sub1} result )
          6# 找到了輸出 0 ,否則為 -1
          7message(${result})
          8
          9# 將字符串全部大寫
          10string(TOUPPER ${var} result)
          11message(${result})
          12
          13# 求字符串的長度
          14string(LENGTH ${var} num)
          15message(${num})

          另外,通過空白或者分隔符號可以表示字符串序列。

          1set(foo this is a list) // 實際內(nèi)容為字符串序列
          2message(${foo})

          當(dāng)字符串中需要用到空白或者分隔符時,再用雙括號""表示為同一個字符串內(nèi)容。

          1set(foo "this is a list") // 實際內(nèi)容為一個字符串
          2message(${foo})

          文件操作

          CMake 中通過 file 來實現(xiàn)文件操作,包括文件讀寫、下載文件、文件重命名等。

          具體查看 官方文檔

          1# 文件重命名
          2file(RENAME "test.txt" "new.txt")
          3
          4# 文件下載
          5# 把文件 URL 設(shè)定為變量
          6set(var "http://img.zcool.cn/community/0117e2571b8b246ac72538120dd8a4.jpg")
          7
          8# 使用 DOWNLOAD 下載
          9file(DOWNLOAD ${var} "/Users/glumes/CLionProjects/HelloCMake/image.jpg")

          在文件的操作中,還有兩個很重要的指令 GLOBGLOB_RECURSE 。

          1# GLOB 的使用
          2file(GLOB ROOT_SOURCE *.cpp)
          3# GLOB_RECURSE 的使用
          4file(GLOB_RECURSE CORE_SOURCE ./detail/*.cpp)

          其中,GLOB 指令會將所有匹配 *.cpp 表達(dá)式的文件組成一個列表,并保存在 ROOT_SOURCE 變量中。

          GLOB_RECURSE 指令和 GLOB 類似,但是它會遍歷匹配目錄的所有文件以及子目錄下面的文件。

          使用  GLOBGLOB_RECURSE 有好處,就是當(dāng)添加需要編譯的文件時,不用再一個一個手動添加了,同一目錄下的內(nèi)容都被包含在對應(yīng)變量中了,但也有弊端,就是新建了文件,但是 CMake 并沒有改變,導(dǎo)致在編譯時也會重新產(chǎn)生構(gòu)建文件,要解決這個問題,就是動一動 CMake,讓編譯器檢測到它有改變就好了。

          預(yù)定義的常量

          在 CMake 中有許多預(yù)定義的常量,使用好這些常量能起到事半功倍的效果。

          • CMAKE_CURRENT_SOURCE_DIR

            • 指當(dāng)前 CMake 文件所在的文件夾路徑

          • CMAKE_SOURCE_DIR

            • 指當(dāng)前工程的 CMake 文件所在路徑

          • CMAKE_CURRENT_LIST_FILE

            • 指當(dāng)前 CMake 文件的完整路徑

          • PROJECT_SOURCE_DIR

            • 指當(dāng)前工程的路徑

          比如,在 add_library 中需要指定 cpp 文件的路徑,以 CMAKE_CURRENT_SOURCE_DIR 為基準(zhǔn),指定 cpp 相對它的路徑就好了。

          1# 利用預(yù)定義的常量來指定文件路徑
          2add_library( # Sets the name of the library.
          3             openglutil
          4             # Sets the library as a shared library.
          5             SHARED
          6             # Provides a relative path to your source file(s).
          7             ${CMAKE_CURRENT_SOURCE_DIR}/opengl_util.cpp
          8             )

          平臺相關(guān)的常量

          CMake 能夠用來在 Window、Linux、Mac 平臺下進行編譯,在它的內(nèi)部也定義了和這些平臺相關(guān)的變量。

          具體查看 官方文檔 (https://cmake.org/cmake/help/v3.12/manual/cmake-variables.7.html) 。

          列舉一些常見的:

          • WIN32

            • 如果編譯的目標(biāo)系統(tǒng)是 Window,那么 WIN32 為 True 。

          • UNIX

            • 如果編譯的目標(biāo)系統(tǒng)是 Unix 或者類 Unix 也就是 Linux ,那么 UNIX 為 True 。

          • MSVC

            • 如果編譯器是 Window 上的 Visual C++ 之類的,那么 MSVC 為 True 。

          • ANDROID

            • 如果目標(biāo)系統(tǒng)是 Android ,那么 ANDROID 為 1 。

          • APPLE

            • 如果目標(biāo)系統(tǒng)是 APPLE ,那么 APPLE 為 1 。

          有了這些常量做區(qū)分,就可以在一份 CMake 文件中編寫不同平臺的編譯選項。

          1if(WIN32){
          2    # do something
          3}elseif(UNIX){
          4    # do something
          5}

          函數(shù)、宏、流程控制和選項 等命令

          具體參考 cmake-commands (https://cmake.org/cmake/help/v3.12/manual/cmake-commands.7.html) ,這里面包括了很多重要且常見的指令。

          簡單示例 CMake 中的函數(shù)操作:

          1function(add a b)
          2    message("this is function call")
          3    math(EXPR num "${a} + $" )
          4    message("result is ${aa}")
          5endfunction()
          6
          7add(1 2)

          其中,function 為定義函數(shù),第一個參數(shù)為函數(shù)名稱,后面為函數(shù)參數(shù)。

          在調(diào)用函數(shù)時,參數(shù)之間用空格隔開,不要用逗號。

          宏的使用與函數(shù)使用有點類似:

          1macro(del a b)
          2    message("this is macro call")
          3    math(EXPR num "${a} - $")
          4    message("num is ${num}")
          5endmacro()
          6
          7del(1 2)

          在流程控制方面,CMake 也提供了 if、else 這樣的操作:

          1set(num 0)
          2if (1 AND ${num})
          3    message("and operation")
          4elseif (1 OR ${num})
          5    message("or operation")
          6else ()
          7    message("not reach")
          8endif ()

          其中,CMake 提供了 AND、OR、NOT、LESSEQUAL 等等這樣的操作來對數(shù)據(jù)進行判斷,比如 AND 就是要求兩邊同為 True 才行。

          另外 CMake 還提供了循環(huán)迭代的操作:

          1set(stringList this is string list)
          2foreach (str ${stringList})
          3    message("str is ${str}")
          4endforeach ()

          CMake 還提供了一個 option 指令。

          可以通過它來給 CMake 定義一些全局選項:

          1option(ENABLE_SHARED "Build shared libraries" TRUE)
          2
          3if(ENABLE_SHARED)
          4    # do something
          5else()
          6    # do something   
          7endif()

          可能會覺得 option 無非就是一個 True or False 的標(biāo)志位,可以用變量來代替,但使用變量的話,還得添加 ${} 來表示變量,而使用 option 直接引用名稱就好了。

          CMake 閱讀實踐

          明白了上述的 CMake 語法以及從官網(wǎng)去查找陌生的指令意思,就基本上可以看懂大部分的 CMake 文件了。

          這里舉兩個開源庫的例子:

          • https://github.com/g-truc/glm

            • glm 是一個用來實現(xiàn)矩陣計算的,在 OpenGL 的開發(fā)中會用到。

            • CMakeLists.txt 地址在 https://github.com/g-truc/glm/blob/master/CMakeLists.txt

          • https://github.com/libjpeg-turbo/libjpeg-turbo

            • libjpeg-turbo 是用來進行圖片壓縮的,在 Android 底層就是用的它。

            • CMakeLists.txt 地址在 https://github.com/libjpeg-turbo/libjpeg-turbo/blob/master/CMakeLists.txt

          這兩個例子中大量用到了前面所講的內(nèi)容,可以試著讀一讀增加熟練度。

          為編譯的庫設(shè)置屬性

          接下來再回到用 CMake 編譯動態(tài)庫的話題上,畢竟 Android NDK 開發(fā)也主要是用來編譯庫了,當(dāng)編譯完 so 之后,我們可以對它做一些操作。

          通過 set_target_properties 來給編譯的庫設(shè)定相關(guān)屬性內(nèi)容,函數(shù)原型如下:

          1set_target_properties(target1 target2 ...
          2                      PROPERTIES prop1 value1
          3                      prop2 value2 ...)

          比如,要將編譯的庫改個名稱:

          1set_target_properties(native-lib PROPERTIES OUTPUT_NAME "testlib" )

          更多的屬性內(nèi)容可以參考官方文檔 (https://cmake.org/cmake/help/v3.9/manual/cmake-properties.7.html#target-properties)。

          不過,這里面有一些屬性設(shè)定無效,在 Android Studio 上試了無效,在 CLion 上反而可以,當(dāng)然也可能是我使用姿勢不對。

          比如,實現(xiàn)動態(tài)庫的版本號:

          1set_target_properties(native-lib PROPERTIES VERSION 1.2 SOVERSION 1 )

          對于已經(jīng)編譯好的動態(tài)庫,想要把它導(dǎo)入進來,也需要用到一個屬性。

          比如編譯的 FFmpeg 動態(tài)庫,

          1# 使用 IMPORTED 表示導(dǎo)入庫
          2add_library(avcodec-57_lib SHARED IMPORTED)
          3# 使用 IMPORTED_LOCATION 屬性指定庫的路徑
          4set_target_properties(avcodec-57_lib PROPERTIES IMPORTED_LOCATION
          5                        ${CMAKE_CURRENT_SOURCE_DIR}/src/main/jniLibs/armeabi/libavcodec-57.so )

          鏈接到其他的庫

          如果編譯了多個庫,并且想庫與庫之間進行鏈接,那么就要通過 target_link_libraries 。

          1target_link_libraries( native-lib
          2                       glm
          3                       turbojpeg
          4                       log )

          在 Android 底層也提供了一些 so 庫供上層鏈接使用,也要通過上面的方式來鏈接,比如最常見的就是 log 庫打印日志。

          如果要鏈接自己編譯的多個庫文件,首先要保證每個庫的代碼都對應(yīng)一個 CMakeLists.txt 文件,這個 CMakeLists.txt 文件指定當(dāng)前要編譯的庫的信息。

          然后在當(dāng)前庫的 CMakeLists.txt 文件中通過 ADD_SUBDIRECTORY 將其他庫的目錄添加進來,這樣才能夠鏈接到。

          1ADD_SUBDIRECTORY(src/main/cpp/turbojpeg)
          2ADD_SUBDIRECTORY(src/main/cpp/glm)

          添加頭文件

          在使用的時候有一個容易忽略的步驟就是添加頭文件,通過 include_directories 指令把頭文件目錄包含進來。

          這樣就可以直接使用 #include "header.h" 的方式包含頭文件,而不用  #include "path/path/header.h" 這樣添加路徑的方式來包含。

          小結(jié)

          以上,就是關(guān)于 CMake 的部分總結(jié)內(nèi)容。

          歡迎關(guān)注微信公眾號:【音視頻開發(fā)進階】,獲得最新文章推送~~~

          掃碼關(guān)注


          瀏覽 102
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  大奶在线97 | 亚洲激情小说 | 黑人操亚州人 | 久久五月情 | 黄片你懂得|