這篇主要介紹CMake語法學習以及怎樣把上一篇文章中編譯生成的交叉編譯庫導入到Android項目中
由于其他原因耽誤導致這篇文章間隔這么久才寫好,導入的時候才發現上一篇文章中交叉編譯后的庫有點問題又進行重新修改。這篇文章是NDK系列的最后一篇了,算是大概知道了NDK系列的入門知識。
NDK系列文章
mk
之前是使用,現在Google基本放棄了,都采用CMake
CMake
在學習CMake之前,我們或多或少了解過其他Make工具。其他的Make工具遵循不同的標準到時執行的Makefile格式不同,比如要保證在不同平臺下編譯,就需要一個跨平臺的Make工具,CMake就是一個跨平臺的構建工具。
Cmake并不直接構建出最終的軟件,通過編寫CmakeList.txt文件,根據目標用戶的平臺進一步生成對應的Makefile文件,從而達到跨平臺的目的,如Android Studio就是通過ninja。所以可以這么理解為,編寫Cmake語法,通過ninja,生成Makefile。
安裝CMake
brew install cmake
# 查看版本
cmake -version
cmake version 3.13.2
CMake語法入門
-
官網語法手冊(最好的資料)
學下CMake語法,可以參考官網的文檔,進入官網,然后Resource --> Documentation --> 選擇最新的Documentation --> Reference Manuals中的cmake-commands(7)。就可以看到所有的命令了。
實踐
Demo1 單文件
main.c文件
#include <stdio.h>
int main(){
printf("hello world\n");
return 0;
}
CMakeLists.txt文件
# CMake 最低版本號要求
cmake_minimum_required (VERSION 3.4.1)
# 項目信息(如果不設置該參數也可以正常運行)
project (MyProject1)
# 指定生成目標
add_executable(Demo1 main.c)
利用CMake命令編譯生成執行文件
# 編譯生成Makefile文件
cmake .
# 執行Makefile文件生成可執行文件
make
# 執行可執行文件,輸出hello world
./Demo1
hello world
Demo2 多文件
如果源文件有很多個,比如有main.c、hello.c、hello.h等,那么一個個寫進去比較麻煩,如
main.c文件
#include "hello.h"
int main()
{
hello("world");
return 0;
}
hello.h文件
#ifndef HELLO_H
#define HELLO_H
void hello(const char *name);
#endif //HELLO_H
hello.c文件
#include <stdio.h>
void hello(const char *name)
{
printf("Hello %s!\n", name);
}
CMakeLists.txt
# CMake 最低版本號要求
cmake_minimum_required (VERSION 3.4.1)
# 項目信息
project (MyProject2)
# 這樣多的話,寫進去就比較麻煩,導入只需要寫源文件,.h可以不寫
add_executable(Demo2 main.c hello.c)
# 查找當前目錄所有源文件 并將名稱保存到 DIR_SRCS 變量
# 注意這樣不能查找子目錄,也不會自動往子目錄找
aux_source_directory(. DIR_SRCS)
# 或者也可以使用如果通配符的方式,引入所有.c文件,同樣保存到 DIR_SRCS變量中
file(GLOB DIR_SRCS *.c)
# $() 為引用變量DIR_SRCS的值
add_executable(Demo2 ${DIR_SRCS})
Demo3 多文件多目錄
新建文件夾helloDir,把Demo2中的hello.c、hello.h和新建一個新的CMakeLists.txt文件放到該目錄下
main.c文件
// 記得修改頭文件的引用路徑
#include "helloDir/hello.h"
int main()
{
hello("world");
return 0;
}
CMakeLists.txt
# CMake 最低版本號要求
cmake_minimum_required (VERSION 3.4.1)
# 項目信息
project (MyProject3)
# 添加當前目錄下的所有源文件
aux_source_directory(. DIR_SRCS)
# 添加 helloDir 子目錄下的cmakelist
add_subdirectory(helloDir)
# 指定生成目標
add_executable(Demo3 ${DIR_SRCS})
# 添加鏈接庫,hello為helloDir子目錄的生成的鏈接庫名稱
target_link_libraries(Demo3 hello)
helloDir/CMakeLists.txt
# CMake 最低版本號要求
cmake_minimum_required (VERSION 3.4.1)
# 指定生成目標
aux_source_directory(. DIR_LIB_SRCS)
# 生成鏈接庫,默認編譯為靜態庫
# add_library (hello ${DIR_LIB_SRCS})
# 指定編譯為靜態庫,與上面的一樣
add_library (hello STATIC ${DIR_LIB_SRCS})
# 指定編譯為動態庫
add_library (hello SHARED ${DIR_LIB_SRCS})
add_library (hello STATIC ${DIR_LIB_SRCS})
NDK項目之CMake
通過Android Stuido創建支持NDK的項目,就可以看到,多了一個CMakeList.txt文件。在Android Studio 2.2及其以上,構建原生庫的默認工具就是CMake。
# 設置cmake最低支持版本
cmake_minimum_required(VERSION 3.4.1)
# 編譯library庫
add_library(
# 設置編譯后的library名稱
native-lib
# 設置library模式,STATIC為靜態庫,如果想編譯為動態庫,則修改為SHARED
STATIC
# 設置編譯使用到的源代碼,如果添加了源代碼,都需要修改此處添加源代碼的路徑
src/main/cpp/native-lib.cpp )
# 查找library,因為NDK中已經有一部分預構建庫,已經被配置為cmake搜索路徑的一部分,所以,可以直接添加庫的名稱。
find_library(
# 此處為查找名稱為log的庫,將絕對路徑賦值到變量log-lib
log-lib
log )
# 鏈接library
target_link_libraries(
# 鏈接自己編寫的native-lib庫和上面查找到的lib庫
# 位置不能更改,native-lib為目標的庫,必須放在前面
native-lib
${log-lib} )
# 如果上面不預先使用find_library的話,也可以直接使用
target_link_libraries( native-lib
log)
同時build.gradle文件也多了一些配置
android {
compileSdkVersion 28
defaultConfig {
externalNativeBuild {
// 主要是配置了cmake的命令參數
cmake {
cppFlags ""
// 設置編譯c/c++ 源文件的cpu類型
// 如果不設置,連接不同的設備,gralde會自動根據連接的設備,如armeabi-v7a架構的手機,則編譯出armeabi-v7a,如模擬器,則編譯出x86。
// 由于考慮編譯出來后包的大小,一般實際開發中,都只是編譯兼容性最多的armeabi-v7a架構
abiFilters 'armeabi-v7a'
}
}
}
externalNativeBuild {
// 主要定義了CMake的構建腳本CMakeLists.txt的路徑
cmake {
// 當前的CMakeLists.txt與build.gradle在同一個目錄下
// CMakeLists.txt也可以在其他路徑下,比如可以在cpp目錄下創建一個CMakeList.txt配置cpp目錄下的源文件
path "CMakeLists.txt"
}
}
}
添加預編譯庫
在上一篇文章中,分別對怎樣編譯Android平臺上的靜、動態庫進行介紹,接下來,就把其編譯好的庫(即預編譯庫)放到Android Stuido項目中
引入靜態庫
引入libmain.a
的靜態庫,在新建armeabi-v7a
在cpp文件下,把libmain.a
放入其中。
cmake_minimum_required(VERSION 3.6)
add_library( native-lib
SHARED
src/main/cpp/native-lib.cpp )
# hello-jni: 變量名 最終會生成的so名字
# SHARED: 動態庫 STATIC:靜態庫
add_library(hello-jni SHARED hello-jni.c)
# 引入預編譯庫
# Main:引入的庫的名字,可以隨便定義,但是要和下面的保存一致
# STATIC:聲明導入的是動態庫或者靜態庫,STATIC為靜態庫
# IMPORTED:表示這個庫是以導入的方式導入
add_library(Main STATIC IMPORTED)
# 設置導入的路徑
# Main:庫的名稱
# 設置目標屬性 導入路徑 當前庫的路徑
# ${ANDROID_ABI}引用的變量,在上面的build.grale中,我們設置abiFilters為armeabi-v7a
set_target_properties(Main PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/src/main/cpp/${ANDROID_ABI}/libmain.a)
find_library( log-lib
log )
target_link_libraries(hello-jni Main)
修改native-lib.cpp
文件,修改后如下
#include <jni.h>
#include <string>
#include <android/log.h>
// 使用上面的log庫
// 該文件為c++,庫為c,因此需要添加extern "C"
extern "C" int main();
extern "C" JNIEXPORT jstring
JNICALL
Java_cn_guidongyuan_studyndk_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
// 如果輸出結果與庫中main定義的返回值一致,則表示調用成功
__android_log_print(ANDROID_LOG_ERROR,"Log","%d", main());
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
引入動態庫
引入libTest.so
動態庫,動態庫必須放在src/jniLibs/armeabi-v7a(不同CPU架構不同目錄)
下,否則不會打包進去
cmake_minimum_required(VERSION 3.6)
add_library( native-lib
SHARED
src/main/cpp/native-lib.cpp )
find_library( log-lib
log )
# 設置庫的查找路徑
# set方法 定義一個變量,此處為在原來的CMAKE_CXX_FLAGS變量上,添加-L加庫的查找路徑
# 因為項目中的native-lib.cpp為C++,因此存在C++則使用CMAKE_CXX_FLAGS,完全沒有就使用CMAKE_C_FLAGS
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -L${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI} ")
# 鏈接動態庫的名字,庫名詞為libmain.so,這里必須使用main,就會結合上面設置的庫查找路徑以及庫名稱查找到對應的庫
target_link_libraries( native-lib
${log-lib}
main)
message("ANDROID_ABI : ${ANDROID_ABI}")
# set 方法 定義一個變量
#CMAKE_C_FLAGS = "${CMAKE_C_FLAGS} XXXX"
# -L: 庫的查找路徑 libTest.so
# 如果庫用c寫的,則用CMAKE_C_FLAGS,如果使用c++或者c++和c混合使用,則用CMAKE_CXX_FLAGS
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -L${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI} ")
find_library(log-lib log)
# 此處的Test名字,必須為動態庫的名稱,查找路徑在上面的set設置了
target_link_libraries(hello-jni Test ${log-lib} )
修改native-lib.cpp
文件,修改內容與上面靜態庫一樣
參考資料
-
很詳細的一篇文章,從實例入手,講解 CMake 的常見用法,單獨作為一種語法來學習,而不是在Android Studio項目中進行介紹。
-
可以看一下其中的CMake常用命令章節,基本列出了CMake語法中常見的語法
-
Android NDK開發(一) 使用CMake構建工具進行NDK開發
簡單介紹了CMake在Android Stuido中的配置,各個變量的作用