GVKun编程网logo

【NDK】【011】CMakeLists语法详解(cmakelists 语法)

20

想了解【NDK】【011】CMakeLists语法详解的新动态吗?本文将为您提供详细的信息,我们还将为您解答关于cmakelists语法的相关问题,此外,我们还将为您介绍关于androidCMakeL

想了解【NDK】【011】CMakeLists语法详解的新动态吗?本文将为您提供详细的信息,我们还将为您解答关于cmakelists 语法的相关问题,此外,我们还将为您介绍关于android CMakeLists、AndroidNDK——makefile语法详解、c – cmake – CMakeLists.txt不在根文件夹中(但包含在源文件夹中)、c – 从现有的Makefile创建CMakeLists文件的新知识。

本文目录一览:

【NDK】【011】CMakeLists语法详解(cmakelists 语法)

【NDK】【011】CMakeLists语法详解(cmakelists 语法)

在介绍CMakeLists语法前,首先建议大家使用CMakeLists替代Android.mk来进行NDK开发

cmake/CMakeLists本来对make/Makefile进行优化后的产物,它的语法更加贴近编程语言,更易读懂


	#注意,这不是一个可实际运行的CMakeLists,仅用于演示语法
	#里面会有重复定义的变量和模块名,不要觉得奇怪,仅仅是为了演示
	
	#指定cmake最小版本
	cmake_minimum_required(VERSION 3.6.0)
	
	#设置C++语法标准版本
	set(CMAKE_CXX_STANDARD 11)
	
	#设置忽略警告
	add_deFinitions(-Wno-error=format-security)
	
	#打印变量
	message("ANDROID_ABI  ${ANDROID_ABI}")
	
	#指定头文件查找目录
	include_directories(src/main/cmake/include)
	
	#引入其它目录的cmakelist
	add_subdirectory(xxx)
	
	#通过指定源文件,构建动态库
	#这个指令会生成动态库文件libhello.so
	add_library(hello SHARED src/main/cmake/main.cpp)
	
	#批量引入源文件
	#这个指令只会检索单个文件夹,不会检索子文件夹
	file(GLOB source src/main/cmake/*.c src/main/cmake/*.cpp)
	add_library(hello SHARED ${source})
	
	#引入预编译好的静态库
	add_library(test STATIC IMPORTED)
	set_target_properties(test PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/src/main/cmake/${ANDROID_ABI}/libtest.a)
	
	#如果要引入预编译好的动态库,需要添加以下配置
	#这个配置,使用项目内的相对路径作为so库的查找路径
	#如果不这样配置,默认将使用绝对路径,作为so库的查找路径,在运行时将会报错
	#如果项目中包含C++代码,请使用CMAKE_CXX_FLAGS代替CMAKE_C_FLAGS
	set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -L${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}")
	
	#在NDK系统目录下,查找liblog.so文件,并将路径赋值给变量libLog
	find_library(libLog log)
	
	#链接自定义模块和系统模块,到要构建的动态库
	target_link_libraries(hello test ${libLog})
	
	#对于安卓6.0以上的系统,如果一个so库引用了另一个预编译好的so库,那么在代码里,只需加载主的so库即可
	System.loadLibrary("hello");
	
	#执行条件判断语句
	#逻辑判断 计较字符串
	set(ANDROID_ABI "areambi-v7a")
	if(${ANDROID_ABI} STREQUAL "areambi")
	  	message("armv5")
	elseif(${ANDROID_ABI} STREQUAL "areambi-v7a")
		message("armv7a")
	else()
		message("other abi")
	endif()
	
	#在Gradle中,可以通过以下选项,配置cmake参数
	externalNativeBuild {
		cmake {
			arguments "-DANDROID_TOOLCHAIN=clang", "-DANDROID_STL=gnustl_static"
			cFlags ""
			cppFlags "" 
		}
	}

android CMakeLists

android CMakeLists

https://developer.android.google.cn/studio/projects/configure-cmake

https://blog.csdn.net/songmingzhan/article/details/80720903

 

# Sets the minimum version of CMake required to build the native
# library. You should either keep the default value or only pass a
# value of 3.4.0 or lower.

cmake_minimum_required(VERSION 3.4.1)

# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds it for you.
# Gradle automatically packages shared libraries with your APK.

add_library(samba_client
            SHARED
            src/main/cpp/jni_helper/JniHelper.cc
            src/main/cpp/jni_helper/JavaClassCache.cc
            src/main/cpp/samba_client/SambaClient.cc
            src/main/cpp/credential_cache/CredentialCache.cc
            )

include_directories(src/main/cpp)

set(CMAKE_CXX_FLAGS
    "${CMAKE_CXX_FLAGS} -std=c++0x -O2 -D_FORTIFY_SOURCE=2 -fstack-protector-all -fPIE")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -pie")

# Searches for a specified prebuilt library and stores the path as a
# variable. Because system libraries are included in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.

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 )

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in the
# build script, prebuilt third-party libraries, or system libraries.

#set(libfolder "${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}")
set(libfolder "${CMAKE_SOURCE_DIR}/libs/${ANDROID_ABI}")
add_library(libsmbclient
            SHARED
            IMPORTED)
set_target_properties(libsmbclient
                      PROPERTIES IMPORTED_LOCATION
                      ${libfolder}/libsmbclient.so)

target_link_libraries(samba_client ${log-lib} libsmbclient)

 

AndroidNDK——makefile语法详解

AndroidNDK——makefile语法详解

https://juejin.im/post/5e43a96df265da5754778608


一、编译流程详解

编译流程

  • 编译:将高级语言编写的程序转换为二进制代码可执行性目标程序的过程
  • 四大过程:预处理、编译、汇编、链接

1、预处理

完成宏替换、文件引入,以及去除空行、注释等,为下一步的编译做准备;也就是对各种预处理命令进行处理,包括头文件的包含、宏定义的扩展、条件编译的选择等。

// test.c文件内容
#include <stdio.h>
int main(){
	printf("hello world!\n");
	return 0;
}
复制代码

对test.c文件进行预处理:

$ gcc -E test.c -o test.i
复制代码
  • 选项-E:让gcc在预处理结束后停止编译,test.i文件为预处理后输出的文件。
  • 选项-o:指定输出文件。

此时,test.i 就是 test.c 预编译后的产物,体积会增大,此时test.i还是一个文本文件,可以用文本编译器打开查看。

2、编译

  • 将预处理后的代码编译成汇编代码。在这个阶段中,首先要检查代码的规范性、是否有语法错误等,以确定代码实际要做的工作,在检查无误后,再把代码翻译成汇编语言。
  • 编译程序执行时,先分析,后综合。分析,就是指词法分析、语法分析、语义分析和中间代码生成。综合,就是指代码优化和代码生成。
  • 大多数的编译程序直接产生机器语言的目标代码,形成可执行的目标文件,也有的是先产生汇编语言一级的符号代码文件,再调用汇编程序进行翻译和加工处理,最后产生可执行的机器语言目标文件。
extern int ftrylockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ;

extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
# 868 "/usr/include/stdio.h" 3 4

# 2 "test.c" 2

# 3 "test.c"
int main(){
 printf("hello world\n");
 return 0;
}
复制代码

上面是预处理后test.i文件的部分内容,下面对test.i文件进行编译:

$ gcc -S test.i -o test.s
复制代码
  • 选项-S:让gcc在编译结束后停止编译过程,"test.s"文件为编译后生成的汇编代码。

此时,test.s 就是 test.i 文件汇编后的产物,同样也可以用文本编译器打开查看。

3、汇编

汇编就是把编译阶段生成的".s"文件转成二进制目标代码,也就是机器代码(01序列)。

	.file	"test.c"
	.text
	.section	.rodata
.LC0:
	.string	"hello world"
	.text
	.globl	main
	.type	main, @function
main:
.LFB0:
	.cfi_startproc
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	leaq	.LC0(%rip), %rdi
	call	puts@PLT
	movl	$0, %eax
	popq	%rbp
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE0:
	.size	main, .-main
	.ident	"GCC: (Ubuntu 7.4.0-1ubuntu1~18.04.1) 7.4.0"
	.section	.note.GNU-stack,"",@progbits
复制代码

上面是编译后生成的test.s文件里的汇编代码,下面对test.s文件进行汇编:

$ gcc -c test.s -o test.o
复制代码
  • 选项-c:让gcc在汇编结束后停止编译过程,"test.o"文件为汇编后生成的机器码目标文件。

4、链接

链接就是将多个目标文件以及所需的库文件链接生成可执行目标文件的过程。

下面对test.o进行链接:

$ gcc test.o -o test
$ ./test
hello world!
复制代码
  • 选项-o:本质上是一个重命名选项。不使用-o选项时,默认生成的是a.out文件。这里生成的是可执行文件test。
  • ./test执行后输出hello world!

5、简化

一般情况下,我们会使用gcc命令,一步生成可执行文件,简化编译流程:

$ gcc -o test test.c
$ ./test
hello world!
复制代码

二、 静态库与动态库原理

1、 静态库

1) 什么是静态库

  • 静态库实际就是一些目标文件(一般以.o结尾)的集合,静态库一般以.a结尾,只用于生成可执行文件阶段。
  • 在链接步骤中,链接器将从库文件取得所需代码,复制到生成的可执行文件中。这种库称为静态库。其特点是可执行文件中包含了库代码的一份完整拷贝,在编译过程中被载入程序中。缺点就是多次使用就会有多份冗余拷贝,并且对程序的更新、部署和发布会带来麻烦,如果静态库有更新,那么所有使用它的程序都需要重新编译、发布。

2) 生成静态库

  • 首先生成test.o目标文件。
  • 使用ar命令将test.o打包成libtest.a静态库。
# 生成目标文件
$ gcc -c test.c -o test.o
# 使用ar命令将目标文件打包成静态库
$ ar  libtest.a test.o
ar: creating libtest.a
# 使用ar t libtest.a 查看静态库内容
$ar t libtest.a
test.o
复制代码

选项rcs各自的含义:

  • 选项r:更新或增加新文件到静态库中。
  • 选项c:创建一个库,不管存在与否,都创建。
  • 选项s:创建文档索引,在创建较大的库时,能够加快编译速度。

2、 动态库

1)什么是动态库

  • 动态库在链接阶段没有被复制到程序中,而是在程序运行时由系统动态加载到内存中供程序调用。
  • 系统只需载入一次动态库,不同的程序可以得到内存中相同动态库的副本,因此节省了很多内存。

2)生成动态库

  • 首先生成test.o目标文件。
  • 使用-shared和-fPIC参数生成动态库。
# 首先生成目标文件
$ gcc -c test.c -o test.o
# 使用-fPIC和-shared生成动态库
$ gcc -shared -fPIC -o libtest.so test.o
复制代码

fPIC:全称是 Position Independent Code, 用于生成位置无关代码。

3、案例

编写一个工具方法(tool.h + tool.c文件),查找出数组的最大值:

// tool.h 文件
int find_max(int arr[], int n);

// tool.c 文件
#include "tool.h"
int find_max(int arr[], int n){
	int max = arr[0];
	int i;
	for(i = 0; i < n; i++){
		if(arr[i] > max){
			max = arr[i];
		}
	}
	return max;
}
复制代码

在main.c文件中,调用tool.h的find_max函数:

// main.c 文件
#include <stdio.h>
#include "tool.h"

int main(){
	int arr[] = {1,3,5,8,2};
	int max = find_max(arr, 5);
	printf("max = %d\n", max);
	return 0;
}
复制代码

1)编译&使用静态库

编译tool静态库:

# 编译tool.c。可以省略"-o tool.o",默认gcc会生成一个与tool.c同名的.o文件。
$ gcc -c tool.c

# 编译生成libtool.a静态库
$ ar rcs libtool.a tool.o

# 编译main可执行文件。
# -l用来指定要链接的库,后面接库的名字;-L表示编译程序根据指定路径寻找库文件。
$ gcc -o main main.c -L. -ltool

$ ./main
max = 8
复制代码

可以用ldd命令查看main文件依赖了哪些库:

$ ldd main
复制代码

2)编译&使用动态库

# 编译tool.c,生成tool.o
$ gcc -c tool.c

# 编译生成libtool.so动态库
$ gcc -shared -fPIC -o libtool.so tool.o

# 编译main可执行文件
$ gcc -o main main.c -L. -ltool

$ ./main
./main: error while loading shared libraries: libtool.so: cannot open shared object file: No such file or directory
复制代码

注意,当静态库与动态库同名时,gcc会优先加载动态库。即,此时目录下即有libtool.a,又有libtool.so,编译main时指定了-ltool,gcc会链接libtool.so!

可以用ldd命令查看main文件依赖了哪些库:

$ ldd main
复制代码

可以看到,libtool.so找不到,这是因为在系统的默认动态链接库路径下没有这个libtool.so文件,可以在执行之前,给main设置环境变量解决:

# 将当前目录设置到环境变量中
$ LD_LIBRARY_PATH=. ./main
max = 8
复制代码

LD_LIBRARY_PATH 指定查找共享库,即动态链接库时,除默认路径以外,其他的路径。

4、区别总结

载入时刻不同:

  • 静态库:在程序编译时会链接到目标代码中,程序运行时不再需要静态库,因此体积较大。而且每次编译都需要载入静态代码,因此内存开销大。
  • 动态库:在程序编译时不会被链接到目标代码中,而是在程序运行时才被载入,程序运行时需要动态库存在,因此体积较小。而且系统只需载入一次动态库,不同程序可以得到内存中相同的动态库副本,因此内存开销小。

三、makefile走读与语法基础

1、makefile是什么

在一个工程中,源文件很多,按类型、功能、模块分别被存放在若干个目录中,需要按一定的顺序、规则进行编译,这时就需要使用到makefile。

  • makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要重新编译,如何进行链接等操作。
  • makefile就是“自动化编译”,告诉make命令如何编译和链接。

makefile是make工具的配置脚本,默认情况下,make命令会在当前目录下去寻找该文件(按顺序找寻文件名为“GNUmakefile”“makefile”“Makefile”的文件)。

在这三个文件名中,最好使用“Makefile”这个文件名,因为,这个文件名第一个字符为大写,这样有一种显目的感觉。 最好不要用“GNUmakefile”,这个文件是GNU的make识别的。有另外一些make只对全小写的“makefile”文件名敏感。 但是基本上来说,大多数的make都支持“makefile”和“Makefile”这两种默认文件名。

当然,配置文件的文件名也可以不是makefile,比如:config.debug,这时需要通过 -f--file 指定配置文件,即:

# 使用-f
$ make -f config.debug
# 使用--file
$ make --file config.debug
复制代码

2、makefile里有什么

makefile包含以下五个:

  • 显示规则:说明了如何生成一个或多个目标文件。
  • 隐晦规则:make有自动推导功能,可以用隐晦规则来简写makefile。
  • 变量定义:在makefile中可以变量一系列的变量,变量一般是字符串,类似c语言中的宏,当makefile被执行时,其中的变量都会被扩展相应的位置上。
  • 文件指示:包括3个部分:①在makefile引用另一个makefile,类似C语言中的include;②根据条件指定makefile中的有效部分,类似C语言中的预编译#if一样;③定义多行的命令。
  • 注释:只有行注释,使用#字符表示。

3、makefile的规则

target ... : prerequisites ...
	command
或者:
target ... : prerequisites ... ; command
复制代码

若prerequisites与command在同一行,需要用;分隔。 若prerequisites与command不在同一行,则command前面需要用tab键开头。 另外,如果命令太长,可以用\作为换行符。

  • target:目标文件。可以是ObjectFile,也可以是执行文件,还可以是标签(Label);如果有多个文件,可以用空格隔开;可以使用通配符。
  • prerequisites:依赖文件,既要生成那个target所需要的文件或其他target。
  • command:make需要执行的命令。

makefile的作用:

告诉make,文件的依赖关系,以及如何生成目标文件。prerequisites中,如果有一个及以上的文件比target要新的话,target就会被认为是过时的,需要重新生成,command就会被执行,从而生成新的target。

4、makefile示例

# 当前目录存在main.c、tool.c、tool.h三个文件
# 下面是makefile文件内容
main: main.o tool.o
	gcc main.o tool.o -o main
.PHONY: clean
clean:
	-rm main *.o
-----------------------------
// 执行 make 后输出如下:
cc	-c -o main.o main.c
cc	-c -o tool.o tool.c
gcc main.o tool.o -o main
// 并且生成了一个可执行文件main
复制代码
  • -o:指定可执行文件的名称。
  • clean:标签,不会生成“clean”文件,这样的target称之为“伪目标”,伪目标的名字不能和文件名重复。clean一般放在文件最后。
  • .PHONY:显示地指明clean是一个“伪目标”。

make会自动推导main.o、tool.o如何生成。 伪目标的名字不能和文件名重复,即当前目录下,不能有clean文件。 可以通过 make clean 执行删除命令。

5、makefile如何工作

默认方式下,输入make命令后:

  • make会在当前目录下找名字叫“Makefile”或“makefile”的文件。
  • 如果找到,它会找文件中第一个目标文件(target),并把这个target作为最终的目标文件,如前面示例中的“main”。
  • 如果main文件不存在,或main所依赖的.o文件的修改时间要比main文件要新,那么它会执行后面所定义的命令来生成main文件。
  • 如果main所依赖的.o文件也存在,那么main会在当前文件中找目标为.o文件的依赖性,若找到则根据规则生成.o文件。
  • make再用.o文件声明make的终极任务,也就是执行文件“main”。

6、makefile中使用变量

objects = main.o tool.o
main: $(objects)
	gcc $(objects) -o main
.PHONY: clean
clean:
	-rm main $(objects)
-----------------------------
// 执行 make 后输出如下:
cc	-c -o main.o main.c
cc	-c -o tool.o tool.c
gcc main.o tool.o -o main
复制代码
  • 为了makefile的易维护,在makefile中我们可以使用变量。makefile的变量也就是一个字符串,理解成C语言中的宏可能会更好。
  • 比如:我们声明一个变量,叫objects,于是,我们就可以很方便地在我们的makefile中以“$(objects)”的方式来使用这个变量了。

7、makefile中引用其他的makefile

# 语法格式
include <filename>

# 举个例子,你有这样几个 Makefile:a.mk、b.mk、c.mk,还有一个文件叫 # foo.make,以及一个变量$(bar),其包含了 e.mk 和 f.mk

include foo.make *.mk $(bar)
# 等价于:
include foo.make a.mk b.mk c.mk e.mk f.mk

# 如果文件找不到,而你希望make时不理会那些无法读取的文件而继续执行
# 可以在include前加一个减号“-”,如:
-include <filename>
复制代码

使用include关键字可以把其它Makefile包含进来,include语法格式: include <filename>

8、环境变量MAKEFILES

MAKEFILES

如果当前环境中字义了环境变量 MAKEFILES,那么,make会把这个变量中的值做一个类似于 include 的动作。这个变量中的值是其它的 Makefile,用空格分隔。只是,它和include不同的是,从这个环境中引入的Makefile的“目标”不会起作用,如果环境变量中定义的文件发现错误,make也会不理。但是建议不要使用这个环境变量,因为只要这个变量一被定义,那么当你使用make时,所有的Makefile都会受到它的影响。 也许有时候Makefile出现了奇怪的事,那么可以查看当前环境中有没有定义这个变量。

9、Makefile预定义变量

变量名 描述 默认值
CC C语言编译器的名称 cc
CPP C语言预处理器的名称 $(CC) -E
CXX C++语言编译器的名称 g++
RM 删除文件程序的名称 rm -f
CFLAGS C语言编译器的编译选项
CPPFLAGS C语言预处理器的编译选项
CXXFLAGS C++语言编译器的编译选项

10、Makefile自动变量

自动变量 描述
$* 目标文件的名称,不包含扩展名
$@ 目标文件的名称,包含扩展名
$+ 所有的依赖文件,以空格隔开,可能含有重复的文件
$^ 所有的依赖文件,以空格隔开,不重复
$< 依赖项中第一个依赖文件的名称
$? 依赖项中所有比目标文件新的依赖文件

11、Makefile函数

define本质是定义一个多行的变量,没办法直接调用,但可以在call的作用下,当作函数来使用。

不带参数

define FUNC
$(info echo "hello")
endef

$(call FUNC)
--------------------
输出:hello
复制代码

带参数

define FUNC1
$(info echo $(1)$(2))
endef

$(call FUNC1,hello,world)
--------------------
输出:hello world
复制代码

12、make的工作流程

GNU的make工作时的执行步骤如下:

  1. 读入所有的Makefile。
  2. 读入被include的其它Makefile。
  3. 初始化文件中的变量。
  4. 推导隐晦规则,并分析所有规则。
  5. 为所有的目标文件创建依赖关系链。
  6. 根据依赖关系,决定哪些目标要重新生成。
  7. 执行生成命令。

1~5是第一阶段,6~7为第二阶段。在第一阶段中,如果定义的变量被使用了,那么make会把变量展开在使用的位置,但是make并不是完全的马上展开,如果变量出现在依赖关系的规则中,那么只有当这条依赖被决定要使用的时候,变量才会被展开。

三、Android.mk基础

1、Android.mk简介

Android.mk是一个向Android NDK构建系统描述NDK项目的GNU makefile片段。主要用来编译生成以下几种:

  • APK程序:一般的Android应用程序,系统级别的直接push即可。
  • JAVA库:Java类库,编译打包生成JAR文件。
  • C\C++应用程序:可执行的C\C++应用程序。
  • C\C++静态库:编译生成C\C++静态库,并打包成.a文件。
  • C\C++共享库:编译生成共享库,并打包成.so文件。

2、Android.mk基本格式

这是一个简单的Android.mk文件的内容:

# 定义模块当前路径(必须定义在文件开头,只需定义一次)
LOCAL_PATH := $(call my-dir)

# 清空当前环境变量(LOCAL_PATH除外)
include $(CLEAR_VARS)

# 当前模块名(这里会生成libhello-jni.so)
LOCAL_MODULE := hello-jni

# 当前模块包含的源代码文件
LOCAL_SRC_FILES := hello-jni.c

# 表示当前模块将被编译成一个共享库
include $(BUILD_SHARED_LIBRARY)
复制代码
  • my-dir:是由编译系统提供的宏函数,返回当前.mk文件的路径。
  • CLEAR_VARS:是由编译系统提供的变量,指向一个特定的GNU makefile片段,可以清除除了LOCAL_PATH以外的以LOCAL_开头的变量,如:LOCAL_MODULELOCAL_SRC_FILES。这样做是因为编译系统在单次执行中,会解析多个构建文件和模块定义,而以LOCAL_开头的变量是全局变量,所以描述每个模块之前,都会声明CLEAR_VARS变量,可以避免冲突。
  • LOCAL_MODULE:定义当前模块名,模块名必须唯一,而且不能包含空格。模块名为"hello-jni"时,会生成libhello-jni.so,如果模块名为"libhello-jni"时,则生成的还是libhello-jni.so!
  • LOCAL_SRC_FILES:当前模块包含的源文件,当源文件有多个时,用空格隔开。

3、编译多个共享库

一个Android.mk可能编译产生多个共享库模块。

LOCAL_PATH := $(call my-dir)

# 模块1
include $(CLEAR_VARS)
LOCAL_MODULE := module1
LOCAL_SRC_FILES := module1.c
include $(BUILD_SHARED_LIBRARY)

# 模块2
include $(CLEAR_VARS)
LOCAL_MODULE := module2
LOCAL_SRC_FILES := module2.c
include $(BUILD_SHARED_LIBRARY)
复制代码

这里会产生libmodule1.so和libmodule2.so两个动态库。

4、编译静态库

虽然Android应用程序不能直接使用静态库,但静态库可以用来编译动态库。比如在将第三方代码添加到原生项目中时,可以不用直接将第三方源码包括在原生项目中,而是将第三方源码编译成静态库,然后并入共享库。

LOCAL_PATH := $(call my-dir)

# 第三方AVI库
include $(CLEAR_VARS)
LOCAL_MODULE := avilib
LOCAL_SRC_FILES := avilib.c platform_posix.c
include $(BUILD_STATIC_LIBRARY)

# 原生模块
include $(CLEAR_VARS)
LOCAL_MODULE := module
LOCAL_SRC_FILES := module.c
# 将静态库模块名添加到LOCAL_STATIC_LIBRARIES变量
LOCAL_STATIC_LIBRARIES := avilib
include $(BUILD_SHARED_LIBRARY)
复制代码

5、使用共享库共享通用模块

静态库可以保证源代码模块化,但是当静态库与共享库相连时,它就变成了共享库的一部分。在多个共享库的情况下,多个共享库与静态库连接时,需要将通用模块的多个副本与不同的共享库重复相连,这样就增加了APP的大小。这种情况,可以将通用模块作为共享库。

LOCAL_PATH := $(call my-dir)

# 第三方AVI库
include $(CLEAR_VARS)
LOCAL_MODULE := avilib
LOCAL_SRC_FILES := avilib.c platform_posix.c
include $(BUILD_SHARED_LIBRARY)

# 原生模块1
include $(CLEAR_VARS)
LOCAL_MODULE := module1
LOCAL_SRC_FILES := module1.c
LOCAL_SHARED_LIBRARIES := avilib
include $(BUILD_SHARED_LIBRARY)

# 原生模块2
include $(CLEAR_VARS)
LOCAL_MODULE := module2
LOCAL_SRC_FILES := module2.c
LOCAL_SHARED_LIBRARIES := avilib
include $(BUILD_SHARED_LIBRARY)
复制代码

以上的做法必须基于同一个NDK项目。

6、在多个NDK项目间共享模块

  • 首先将avilib源代码移动到NDK项目以外的位置,比如:C:\android\shared-modules\transcode\avilib
  • 作为共享库模块,avilib需要有自己的Android.mk文件。
  • transcode/avilib为参数调用函数宏import-module添加到NDK项目的Android.mk文档末尾。

import-module函数宏在NDK版本r5以后才有。

# avilib模块自己的Android.mk文件
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := avilib
LOCAL_SRC_FILES := avilib.c platform_posix.c
include $(BUILD_SHARED_LIBRARY)
---------------------------------------------
# 使用共享模块的NDK项目1的Android.mk文件
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := module1
LOCAL_SRC_FILES := module1.c
LOCAL_SHARED_LIBRARIES := avilib
include $(BUILD_SHARED_LIBRARY)
$(call import-module,transcode/avilib)
---------------------------------------------
# 使用共享模块的NDK项目2的Android.mk文件
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := module2
LOCAL_SRC_FILES := module2.c
LOCAL_SHARED_LIBRARIES := avilib
include $(BUILD_SHARED_LIBRARY)
$(call import-module,transcode/avilib)
复制代码

当心细的你在看到$(call import-module,transcode/avilib)这句时,一定会问,为什么NDK会知道要去C:\android\shared-modules\目录下面找transcode/avilib呢?是的,NDK并没有这么智能,默认情况下,import-module函数宏只会搜索AndroidNDK下面的sources目录。

如我的NDK路径是:C:\Users\lqr\AppData\Local\Android\Sdk\ndk-bundle,那么import-module函数宏默认的寻找目录就是C:\Users\lqr\AppData\Local\Android\Sdk\ndk-bundle\sources

要正确使用import-module,就需要对NDK_MODULE_PATH进行配置,把C:\android\shared-modules\配置到环境变量中即可,当有多个共享库目录时,用;隔开。

更多关于import-module的介绍,请翻到文末查看。

7、使用预编译库

  • 想在不发布源代码的情况下将模块发布给他人。
  • 想使用共享库模块的预编译版来加速编译过程。

现在我们手上有第三方预编译好的库libavilib.so,想集成到自己项目中使用,则需要在Android.mk中进行如下配置:

# 预编译共享模块的Android.mk文件
LOCAL_PATH := $(call my-dir)
# 第三方预编译的库
include $(CLEAR_VARS)
LOCAL_MODULE := avilib
LOCAL_SRC_FILES := libavilib.so
include $(PREBUILT_SHARED_LIBRARY)
复制代码

可以看到,LOCAL_SRC_FILES指向的不再是源文件,而是预编译好的libavilib.so,相对于LOCAL_PATH的位置。

8、编译独立的可执行文件

为了方便测试和进行快速开发,可以编译成可执行文件。不用打包成APK就可以得到到Android设备上直接执行。

# 独立可执行模块的Android.mk文件
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := module
LOCAL_SRC_FILES := module.c
LOCAL_STATIC_LIBRARIES := avilib
include $(BUILD_EXECUTABLE)
复制代码

9、注意事项

假如我们本地库libhello-jni.so依赖于libTest.so(可以使用NDK下的ndk-depends查看so的依赖关系)。

  • 在Android6.0版本之前,需要在加载本地库前先加载被依赖的so。
  • 在Android6.0版本之后,不能再使用预编译的动态库(静态库没问题)。
// Android 6.0版本之前:
System.loadlibrary("Test");
System.loadlibrary("hello-jni");

// Android 6.0版本之后:  
System.loadlibrary("hello-jni");
复制代码

四、附加

1、import_module 详解

以下内容引用自 《import-module的注意事项与NDK_MODULE_PATH的配置》

c – cmake – CMakeLists.txt不在根文件夹中(但包含在源文件夹中)

c – cmake – CMakeLists.txt不在根文件夹中(但包含在源文件夹中)

我正在尝试编译一个libpng库.问题是我需要这个库的特定版本 – 1.2.37 – 因为我使用它的项目是用这个版本编写的.
我找到了这个版本 here(GnuWin32项目)的源代码.

但文件夹结构看起来像这样:

libpng-1.2.37-src/
   contrib/
   projects/
   scripts/
      CMakeLists.txt
   png.h
   pngread.c
   pngwrite.c
   ...

请参阅,CMakeLists.txt比源文件更深一级.

我试过了:

>源目录libpng-1.2.37-src / – >导致错误:源目录似乎不包含CMakeLists.txt
>源目录libpng-1.2.37-src / scripts – >导致多个错误:文件libpng-1.2.37-src / scripts / scripts / libpng.pc.in不存在.
>将/ scripts中的CMakeLists.txt复制到/libpng-1.2.37-src并将源目录设置为/libpng-1.2.37-src – >导致错误:源“/libpng-1.2.37-src/CMakeLists.txt”与用于生成缓存的源“/libpng-1.2.37-src/scripts/CMakeLists.txt”不匹配.

我该怎么做才能让它发挥作用?我不知道为什么如果不能使用CMakeLists.txt文件将被包括在内.

解决方法

INSTALL文件明确地说:
If you want to use "cmake" (see www.cmake.org),copy CMakeLists.txt
from the "scripts" directory to this directory and type

   cmake . [-dpnG_MMX=YES] -DCMAKE_INSTALL_PREFIX=/path
   make
   make install

作为旁注,在此之前,它说安装它的经典方法是:

On Unix/Linux and similar systems,you can simply type

    ./configure [--prefix=/path]
    make check
    make install

听起来你用3)做对了,但是你再次尝试之前忘了清理构建目录.

c – 从现有的Makefile创建CMakeLists文件

c – 从现有的Makefile创建CMakeLists文件

我想使用cmake生成C项目的构建文件.我有一个现有的Makefile.
使用标准cmake语法生成此Makefile时遇到问题.

如何在cmake的TARGET_LINK_LIBRARIES部分中包含标准的C库,如-lstdc -lpthread -lboost_thread-mt?或者这些文件应该包含在ADD_DEPENDENCIES部分中.

(要么)
有没有一个简单的工具,从Makefile生成一个CMakeList.txt文件

解决方法

不幸的是,从Makefile到CMakeLists没有简单的1:1的转换.由于CMake应该在所有平台上运行,所以它不能依赖平台特定的假设,如GNU make,这在某些地方会使事情变得复杂.

特别是,CMake提供了非常强大而且复杂的使用库的机制:您可以使用库的名称调用find_package,该库将从cmake模块路径调用库搜索脚本.此脚本(也以CMake编写)将尝试检测库头文件和lib文件的位置,并将它们存储在一些CMake变量中,然后将其传递给相应的CMake命令,如include_directories和target_link_libraries.

这种方法有两个问题:首先,您需要一个搜索脚本.幸运的是,CMake附带了Pthreads,Boost等几个搜索脚本,但是如果您使用的是更多异域的图书馆,那么您可能必须自己编写搜索脚本,这是一种神秘的体验.

第二个主要问题是搜索脚本没有返回结果的标准方法.虽然使用的变量有命名约定,但通常不适用.在实践中,这意味着您必须检查搜索脚本的来源以了解如何使用它.幸运的是,CMake附带的脚本大部分文档很好.

内置脚本位于< cmake-install-prefix> /share/cmake-2.8/Modules之类的位置.对于您的问题,请查看FindBoost.cmake和FindThreads.cmake文件(CMake应该自动链接到标准库). Anycorn已经提供了一些使用Boost脚本的示例代码,您需要知道的其他一切都是在CMake文档中或直接在搜索脚本文件中.

今天的关于【NDK】【011】CMakeLists语法详解cmakelists 语法的分享已经结束,谢谢您的关注,如果想了解更多关于android CMakeLists、AndroidNDK——makefile语法详解、c – cmake – CMakeLists.txt不在根文件夹中(但包含在源文件夹中)、c – 从现有的Makefile创建CMakeLists文件的相关知识,请在本站进行查询。

本文标签: