Android-JNI开发系列《八》CMakeLists.txt语法&使用

Posted 顾修忠

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android-JNI开发系列《八》CMakeLists.txt语法&使用相关的知识,希望对你有一定的参考价值。

人间观察

带饭去上班的都是成年人的奢侈品!

技术永远在不断的更新升级,android也一样。

目前在Android中的JNI开发都是采用的CMake进行编译c,c++代码来构建项目,早期都是Android.mk、Application.mk文件来构建项目的。
CMake是啥呢?简单的说它是一个跨平台的编译工具,它可以用简单的配置文件就可以生成编译的中间产物(Makefile 或者 project 文件),然后用make生成可执行的文件。

CMake官网地址 CMake官网

在Android Studio IDE中不需要我们单独安卓CMake,因为NDK已经自带了。

配置文件就是CMakeLists.txt文件,我们需要熟悉它的语法,不然你看JNI项目的都看不懂,那还搞啥呢!

本篇也是自己学习后的记录,之前自己对CMakeLists.txt的了解也较少。

CMakeLists.txt 语法介绍

1是有的项目使用cmake的时候一些函数都是用大写的,CMakeLists.txt文件不区分大小写,所以看到不要觉得奇怪,一样的。
2是这个文件ide不会对api进行提示,所以你写错一点就会很难查。所以写的时候需要特别注意,我们相信Android studio后续版本会支持。

设置cmake的版本

指定cmake的最小版本,用于编译该项目的cmake版本

cmake_minimum_required(VERSION 3.4.1)

设置项目名称

设置项目的名称,这个是可选的。如果设置了会cmake会自动给你定义两个变量:demo_SOURCE_DIR,demo_BINARY_DIR。不过不指定的话默认有PROJECT_SOURCE__DIR ,PROJECT_BINARY_DIR,分别代表项目的源文件目录,项目编译后生成的二进制目录。

project(demo)

调试信息打印

支持变量取值输出$,字符串打印

message("build with debug mode")
message("project source dir=$PROJECT_SOURCE_DIR")
message(WARNING "输出了一条警告信息")

设置生成的类型

用于生成可执行文件还是so库,一般自己调试/写demo的时候使用add_executable,对外提供so用add_library
add_executable(hello hello.cpp) 生成的是可执行文件,其中第一个参数是生成可执行文件的名字,后面的是源文件。就像之前用gcc编译一样,采用 gcc hello.cpp -o hello 命令来生成可执行文件hello

add_executable(hello hello.cpp) # 生成可执行文件
add_library(demo STATIC demo.cpp) # 生成静态库
add_library(demo SHARED demo.cpp) # 生成动态库或共享库

设置需要编译的源文件和头文件

指定头文件目录

include_directories 中可以设置多个如下,include_directories不区分大小写

INCLUDE_DIRECTORIES(
   $PROJECT_SOURCE_DIR/include
   $PROJECT_SOURCE_DIR
)

明确指定包含哪些源文件
add_library(hello  hello.cpp a.cpp b.cpp)
指定目录下的源文件

aux_source_directory(dir var) 第一个是目录,第二个是变量,意思就是把当前工程目录下的 src 目录的下的所有源文件赋值给 SRC_LIST。赋值后用到的时候通过$SRC_LIST即可。

aux_source_directory($PROJECT_SOURCE_DIR/src  SRC_LIST)
add_library(hello  $SRC_LIST)

下面这个方式等同于上面的,意思是当前工程目录下的 fun1 目录的下的所有源文件赋值给 SRC_LIST

file(GLOB SRC_LIST_1 "fun1/*.cpp")
add_library(hello $SRC_LIST_1)

可以调用多次,比如不同的目录设置不同的变量

file(GLOB SRC_LIST_1 "fun1/*.cpp")
file(GLOB SRC_LIST_2 "fun2/*.cpp")
add_library(hello $SRC_LIST_1  $SRC_LIST_2)

当然上面的aux_source_directory也可以调用多次,可以理解成搜索你想要的源文件并设置到多个变量上,比如:

aux_source_directory(fun1 SRC_LIST_1)
aux_source_directory(fun2 SRC_LIST_2)
add_library(hello $SRC_LIST_1  $SRC_LIST_2)

接下来,我们先试一下如上的这些。我们不在Android studio测试,我们直接安装cmake工具测试,这样方便我们更容易理解其中的道理。

如何安装CMake呢 ?

可以参考网上的这篇
CMake的安装

我们先测试一下生成可执行文件并打印的有关的cmake提供的一些常量,我们随便建立一个目录,并在目录下建立一个hello.cpp的源文件和CMakeLists.txt文件代码如下:

#include <stdio.h>
int main(int argc, char* argv[])
		int a=100;
		int b=200;
		printf("a+b=%d\\n",(a+b));
        return 0;

# 指定 cmake 最低编译版本
cmake_minimum_required(VERSION 3.4.1)

# 指定项目名字
project (HELLO)

# 输出打印构建目录
message(STATUS "This is HELLO_BINARY_DIR " $HELLO_BINARY_DIR)
# 输出打印资源目录
message(STATUS "This is HELLO_SOURCE_DIR " $HELLO_SOURCE_DIR)

# 输出打印资源目录,与HELLO_SOURCE_DIR 一样 
message(STATUS "This is PROJECT_BINARY_DIR " $PROJECT_BINARY_DIR)
message(STATUS "This is PROJECT_SOURCE_DIR " $PROJECT_SOURCE_DIR)

# 输出打印 CMake 资源目录,与 PROJECT_SOURCE_DIR 一样 
MESSAGE(STATUS "This is CMAKE_SOURCE_DIR " $CMAKE_SOURCE_DIR)


# 生成可执行文件 hello 
ADD_EXECUTABLE(hello hello.cpp)

开始编译,新建 build 目录(为什么要新建这个make模块一般都是这个,为了生成的中间产物和我们的源代码分离),cd 到 build 目录下,敲 cmake … 命令,然后make指令生成可执行文件。输出:

B000000073160:build guxiuzhong$ cmake ..
-- The C compiler identification is AppleClang 10.0.0.10001044
-- The CXX compiler identification is AppleClang 10.0.0.10001044
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /Library/Developer/CommandLineTools/usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /Library/Developer/CommandLineTools/usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- This is HELLO_BINARY_DIR /Users/guxiuzhong/Desktop/cmake/cmake-hello/build
-- This is HELLO_SOURCE_DIR /Users/guxiuzhong/Desktop/cmake/cmake-hello
-- This is PROJECT_BINARY_DIR /Users/guxiuzhong/Desktop/cmake/cmake-hello/build
-- This is PROJECT_SOURCE_DIR /Users/guxiuzhong/Desktop/cmake/cmake-hello
-- This is CMAKE_SOURCE_DIR /Users/guxiuzhong/Desktop/cmake/cmake-hello
-- Configuring done
-- Generating done
-- Build files have been written to: /Users/guxiuzhong/Desktop/cmake/cmake-hello/build
B000000073160:build guxiuzhong$ make
Scanning dependencies of target hello
[ 50%] Building CXX object CMakeFiles/hello.dir/hello.cpp.o
[100%] Linking CXX executable hello
[100%] Built target hello
B000000073160:build guxiuzhong$ 

可以看到打印的有关的cmake提供的一些常量值。
同时ls 一下会发现 CMake 帮我们生成了 Makefile 等等一些文件。敲 make 命令生成 hello 可执行文件

B000000073160:build guxiuzhong$ ls -al
total 96
drwxr-xr-x   8 guxiuzhong  672505530    256 11  1 13:07 .
drwxr-xr-x   7 guxiuzhong  672505530    224 11  1 13:07 ..
-rw-r--r--@  1 guxiuzhong  672505530   6148 11  1 13:03 .DS_Store
-rw-r--r--   1 guxiuzhong  672505530  13816 11  1 13:07 CMakeCache.txt
drwxr-xr-x  12 guxiuzhong  672505530    384 11  1 13:07 CMakeFiles
-rw-r--r--   1 guxiuzhong  672505530   5315 11  1 13:07 Makefile
-rw-r--r--   1 guxiuzhong  672505530   1558 11  1 13:07 cmake_install.cmake
-rwxr-xr-x   1 guxiuzhong  672505530   8432 11  1 13:07 hello
B000000073160:build guxiuzhong$ ./hello 
a+b=200
B000000073160:build guxiuzhong$ 

来我们接着看其它语法。

查找指定的链接库文件

find_library(var path)查找到指定的预编译库,并把它的路径存储在变量var中。默认的搜索路径为 cmake 包含的系统库,所以NDK 的公共库只需要指定库的 name 即可。比如Android jni中用于打印日志的系统库log,可以如下写法:

find_library( # Sets the name of the path variable.
              log-lib
 
              # Specifies the name of the NDK library that
              # you want CMake to locate.
              log )

设置链接库搜索目录

也可以指定多个目录

link_directories(
    $PROJECT_CURRENT_SOURCE_DIR/libs

###设置本项目生产的库需要链接的其它库

如果编译本项目需要依赖其它的so库,通过如上的查找后,就可以通过下面的就进行设置了。

target_link_libraries( # 目标库
                       hello
 
                       # 目标库需要链接的库
                       # log-lib 是上面 find_library 指定的变量名
                       $log-lib )                     

到这里,我们演示下如何链接别人的库,首先我们先用add_library创建一个库,我们新建一个目录cmake-createso然后里面建立2个名为include src的子目录,里面放头文件和源代码。

include 文件夹下的2个文件

// add.h 文件
#ifndef _ADD_H
#define _ADD_H

#endif
int add(int num1, int num2);

// sub.h
#ifndef _SUB_H
#define _SUB_H

#endif
int sub(int num1, int num2);

src文件夹下的2个文件

// add.cpp
#include "add.h"
int add(int num1, int num2)
        return num1 + num2;

// sub.cpp
#include "sub.h"
int sub(int num1, int num2)
        return num1 - num2;

关键的我们的CMakeLists.txt文件

#版本号
cmake_minimum_required(VERSION 3.4.1)
#项目名字
project (math)
include_directories($PROJECT_SOURCE_DIR/include)
# 指定源文件
file(GLOB SRC_LIST "$PROJECT_SOURCE_DIR/src/*.cpp")
# 指定输出 .so 动态库的目录位置
set(LIBRARY_OUTPUT_PATH $PROJECT_SOURCE_DIR/libs)
# 指定生成动态库
add_library(math SHARED $SRC_LIST)

同样的新建 build 目录,建立完成目录结构如下:

├── build
│      CMakeLists.txt
└── libs
└── include
    └── add.h
    └── sub.h
└── src
    └── add.cpp
    └── sub.cpp

进入到build目录,cmake .. 没有报错的话然后make,成功后会在lib下生成名为math的库,我的是mac电脑生成的是libmath.dylib,如果是在Android studio的ide中生成的是libmath.so

ok ,既然生成了so,解析来我们使用一下。我们在第一次生成hello的demo中链接下libmath.so ,既然是使用so我们需要把include头文件夹和libs库都拷贝到第一次生成hello的demo的build的统计目录下。同时修改下hello.cpp 如下:

#include <stdio.h>
#include "add.h"
#include "sub.h"
int main(int argc, char* argv[])
        int a = 100;
        int b = 200;
        printf("%d+%d=%d\\n",a,b,add(a,b));
        printf("%d-%d=%d\\n",a,b,sub(a,b));
        return 0;

然后target_link_libraries 通过CMakeLists.txt添加链接的so库。如下:

# 指定 cmake 最低编译版本
cmake_minimum_required(VERSION 3.4.1)

project (HELLO)

#指定头文件目录位置
include_directories($PROJECT_SOURCE_DIR/include)

#添加共享库搜索路径
LINK_DIRECTORIES($PROJECT_SOURCE_DIR/libs)

#生成可执行文件
add_library(hello hello.cpp)

#为hello添加共享库链接
target_link_libraries(hello math)

一样进入到build目录,cmake .. 没有报错的话然后make,成功后输出

B000000073160:build guxiuzhong$ make
Scanning dependencies of target hello
[ 50%] Building CXX object CMakeFiles/hello.dir/hello.cpp.o
[100%] Linking CXX executable hello
[100%] Built target hello
B000000073160:build guxiuzhong$ ./hello 
100+200=300
100-200=-100

设置变量

  1. set 直接设置变量的值,比如上面的demo
# 指定输出 .so 动态库的目录位置
set(LIBRARY_OUTPUT_PATH $PROJECT_SOURCE_DIR/libs)
  1. 追加设置变量的值
set(SRC_LIST  hello.cpp)
set(SRC_LIST $SRC_LIST  fun1.cpp)
add_executable(hello  $SRC_LIST)

ok,到这里就结束了。

上面写的这些知识只是简单的了解和入门,不至于下次看到觉得一脸懵逼。
cmake的语法不亚于一种脚本语言,比如if…elseif…else…endif等条件控制。

可以参考官网,或者用到的时候再学习下。
官网开发文档

C/C++ 开发工具推荐CLion

上面的代码都是手写的,太烦了,而且CMakeLists.txt文件也不好写。

最后,推荐CLion,CLion 是 JetBrains 推出的全新的 C/C++ 跨平台集成开发环境。有Mac版本的,界面像Android studio。长这样,是不是很喜欢:

以上是关于Android-JNI开发系列《八》CMakeLists.txt语法&使用的主要内容,如果未能解决你的问题,请参考以下文章

Android-JNI开发系列《十》实践利用libjpeg-turbo完美压缩图片不失真

Android-JNI开发系列《十一》实践-利用Android C源码实现GIF图片的播放

Android-JNI开发系列《九》实战-Bitmap处理实现底片灰度化黑白化暖冷色调等效果

S5PV210开发系列八_Yaffs的移植

[问八系列] Windows 8 开发 : 分离检视型应用程序 Part 1 - 清单 (List)

ES6 系列八:Iterator