目录
上一篇 深入浅出Android NDK之Android.mk基本语法
在之前的深入浅出Android NDK之Hello-Ndk一章我们已经学习了编译一个动态库、静态库、可执行文件所需要的最基本的编译指令。
LOCAL_SRC_FILES 可用于指定编译的源文件列表。
除了源文件列表外,通常在编译时我们还需要指定头文件的搜索路径。
LOCAL_C_INCLUDES可用于指定头文件搜索路径,和LOCAL_SRC_FILES一样,可以使用相对路径,也可以使用绝对路径,多条路径之间使用空格分开。
如下面的例子:
LOCAL_C_INCLUDES := sources/foo ../ ../../test
若想多行书写,必须使用反斜杠加上回车的方式:
LOCAL_C_INCLUDES := sources/foo \
../ \
../../test
之前我们提到过LOCAL_SRC_FILES中的相对路径是相对于$(LOCAL_PATH),那么LOCAL_C_INCLUDES是相对于哪里呢?是否也是相对于$(LOCAL_PATH)?
很遗憾,LOCAL_C_INCLUDES并不是相对于$(LOCAL_PATH),而是相对于工程目录。
也就是相对于jni文件夹的父目录。
下面我们看一个例子,文件结构如下图所示:
test1.h和test2.h都是空文件,test.c的内容如下:
#include "test1.h"
#include "test2.h"
Android.mk的内容如下:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := hello-c
LOCAL_SRC_FILES := test.c
LOCAL_C_INCLUDES := ./ jni/include
include $(BUILD_SHARED_LIBRARY)
LOCAL_C_INCLUDES指定了两个路径./和jni/include。
指定./是为了能够找到test1.h
指定jni/include是为了能够找到test2.h
打开控制台,输入ndk-build编译:
D:\ndk-demo\hello-c>ndk-build
Android NDK: APP_PLATFORM not set. Defaulting to minimum supported version android-16.
[arm64-v8a] Compile : hello-c <= test.c
[arm64-v8a] SharedLibrary : libhello-c.so
[arm64-v8a] Install : libhello-c.so => libs/arm64-v8a/libhello-c.so
[armeabi-v7a] Compile thumb : hello-c <= test.c
[armeabi-v7a] SharedLibrary : libhello-c.so
[armeabi-v7a] Install : libhello-c.so => libs/armeabi-v7a/libhello-c.so
[x86] Compile : hello-c <= test.c
[x86] SharedLibrary : libhello-c.so
[x86] Install : libhello-c.so => libs/x86/libhello-c.so
[x86_64] Compile : hello-c <= test.c
[x86_64] SharedLibrary : libhello-c.so
[x86_64] Install : libhello-c.so => libs/x86_64/libhello-c.so
编译成功,下面我们再做个实验,我们cd到jni文件夹下,执行ndk-build。
D:\ndk-demo\hello-c\jni>ndk-build
Android NDK: APP_PLATFORM not set. Defaulting to minimum supported version android-16.
[arm64-v8a] Compile : hello-c <= test.c
D:/ndk-demo/hello-c/jni/test.c:1:10: fatal error: 'test1.h' file not found
#include "test1.h"
^~~~~~~~~
1 error generated.
make: *** [D:/ndk-demo/hello-c/obj/local/arm64-v8a/objs/hello-c/test.o] Error 1
编译报错了,说找不到头文件。
之前我们讲过,有两种方式调用ndk-build命令:
- cd到工程目录(jni文件夹的父目录),然后运行ndk-build命令
- 在任意目录直接运行ndk-build,通过-C参数传入工程目录
现在我们又发现了一种方式,原来cd到jni目录,执行ndk-build命令也是可以编译的,虽然编译报错了,但cd到jni目录,执行ndk-build确实是可行的。
怎么解决这个错误呢?难道头文件不是相对于工程目录?那又是相对于哪里呢?
严格意义上来说,头文件确实不上相对于工程目录,而是相对于当前目录。
那么当前目录又是什么呢?我们cd到哪里执行的ndk-build命令,哪里就是当前目录。
在我们这个例子中,当我们cd到D:\ndk-demo\hello-c,执行ndk-build时,D:\ndk-demo\hello-c就是当前目录,头文件都是相对于D:\ndk-demo\hello-c的,
LOCAL_C_INCLUDES := ./ jni/include
其实相当于:
LOCAL_C_INCLUDES := D:\ndk-demo\hello-c D:\ndk-demo\hello-c/jni/include
而当我们cd到jni文件夹后执行ndk-build时,D:\ndk-demo\hello-c\jni就是当前目录,这时头文件都是相对于D:\ndk-demo\hello-c\jni的。
LOCAL_C_INCLUDES := ./ jni/include
其实相当于:
LOCAL_C_INCLUDES := D:\ndk-demo\hello-c\jni D:\ndk-demo\hello-c/jni/jni/include
这两个目录中确实找不到test1.h,所以编译器才会报错。
要想能够在jni目录下执行ndk-build成功,我们应该这么指定LOCAL_C_INCLUDES:
LOCAL_C_INCLUDES := ../ include
如果我们在执行ndk-build时可以加上V=1参数,你会发现ndk-build最终是通过clang来编译源文件的,LOCAL_C_INCLUDES最终会转换为clang的-I参数。
试想一下如果你直接通过clang编译一个C源文件,那么-I参数里的相对路径是相对于哪里呢?当然是相对于当前目录。
D:\ndk-demo\hello-c\jni>ndk-build -B V=1
Android NDK: APP_PLATFORM not set. Defaulting to minimum supported version android-16.
LOCAL_PATH is:D:/ndk-demo/hello-c/jni
LOCAL_PATH is:D:/ndk-demo/hello-c/jni
LOCAL_PATH is:D:/ndk-demo/hello-c/jni
LOCAL_PATH is:D:/ndk-demo/hello-c/jni
[arm64-v8a] Compile : hello-c <= test.c
D:/android-ndk-r20b/build//../toolchains/llvm/prebuilt/windows-x86_64/bin/clang.exe -MMD -MP -MF D:/ndk-demo/hello-c/obj/local/arm64-v8a/objs/hello-c/test.o.d -target aarch64-none-linux-android21 -fdata-sections -ffunction-sections -fstack-protector-strong -funwind-tables -no-canonical-prefixes --sysroot D:/android-ndk-r20b/build//../toolchains/llvm/prebuilt/windows-x86_64/sysroot -g -Wno-invalid-command-line-argument -Wno-unused-command-line-argument -fno-addrsig -fpic -O2 -DNDEBUG -I./ -Ijni/include -ID:/ndk-demo/hello-c/jni -DANDROID -Wa,--noexecstack -Wformat -Werror=format-security -c D:/ndk-demo/hello-c/jni/test.c -o D:/ndk-demo/hello-c/obj/local/arm64-v8a/objs/hello-c/test.o
D:/ndk-demo/hello-c/jni/test.c:1:10: fatal error: 'test1.h' file not found
#include "test1.h"
^~~~~~~~~
1 error generated.
make: *** [D:/ndk-demo/hello-c/obj/local/arm64-v8a/objs/hello-c/test.o] Error 1
那如果我们不cd,通过-C参数指定工程目录,头文件又是相对于哪里呢?比如在D盘执行ndk-build -C D:\ndk-demo\hello-c
这时候头文件是不是相对于D盘?并不是的。
之前我们说过ndk-build其实相当于 $GNUMAKE -f /build/core/build-local.mk
-C参数其实是GNUMake中的参数,作用是将当前目录改变到指定的位置后再执行。
D:\ndk-demo\hello-c\jni>D:\android-ndk-r20b\prebuilt\windows-x86_64\bin\make -h
Usage: make [options] [target] ...
Options:
-b, -m Ignored for compatibility.
-B, --always-make Unconditionally make all targets.
-C DIRECTORY, --directory=DIRECTORY
Change to DIRECTORY before doing anything.
所以ndk-build 的两种编译方式,效果其实是完全一样的,区别仅仅在于,一个是我们自己cd到工程目录,一个是ndk-build在执行make命令时帮我们cd到工程目录。
所以,
ndk-build -C D:\ndk-demo\hello-c的头文件是相对于D:\ndk-demo\hello-c的
ndk-build -C D:\ndk-demo\hello-c\jni的头文件是相对于 D:\ndk-demo\hello-c\jni的
这也太恶心了,在工程目录和jni目录下执行ndk-build,LOCAL_C_INCLUDES竟然需要不同的配置。
怎么解决这个问题呢?
不管当前目录在哪里,LOCAL_PATH总是指向Android.mk所在目录,也就是jni目录。
所以我们可以向下面这样指定:
LOCAL_C_INCLUDES := $(LOCAL_PATH)/../ $(LOCAL_PATH)/include
这么指定的意思是,让我们的头文件和源文件一样,都是相对于$(LOCAL_PATH)的。
这样LOCAL_C_INCLUDES和当前目录就没什么关系了,不管当前目录在哪里,我们的指向总是对的。
强烈建议使用这种方式来指定头文件搜索路径,在后续章节中我们会讲,如何直接在Android Studio使用编译C/C++代码。若在Android Studio中,则必须使$(LOCAL_PATH)来指定头文件搜索路径,否则会出现找不到头文件的情况,原因是因为Android Studio在编译的时候没有指定工程目录,所以头文件没有办法再相对于工程目录了。
LOCAL_CFLAGS也是我们经常会设置的一个选项,之前说过ndk-build最终通过clang来编译源文件,LOCAL_CFLAGS允许我们向编译器传递编译选项,所以LOCAL_CFLAGS其实是和编译器相关的。要想完全弄明白应该查询编译器的用法。
可以运行下面这个命令,来查询clang的用法。
>D:/android-ndk-r20b/toolchains/llvm/prebuilt/windows-x86_64/bin/clang.exe --help
所以LOCAL_C_INCLUDES其实完全可以用LOCAL_CFLAGS来代替,像下面这样:
LOCAL_CFLAGS := -I$(LOCAL_PATH)/../ -I$(LOCAL_PATH)/include
对于写代码来说,我们经常用的可能就是指定警告级别、定义宏。
先给一个定义警告级别的例子:
LOCAL_CFLAGS := -Wno-narrowing -Wall
写C代码经常会用到宏,比如:
#ifdef __LOG_OPEN__
XXXXXXXXXX
#endif
#if __SRM__ == 0
XXXXXX
#elif __SRM__ == 1
XXXXXX
#endif
对于__LOG_OPEN__和__SRM__ 两个宏,我们可以在编译时指定:
LOCAL_CFLAGS := -D__LOG_OPEN__ -D__SRM__=1
-D参数后面紧跟宏的名字,如果宏有值则紧跟等号再紧跟值
LOCAL_CPPFLAGS和LOCAL_CFLAGS的作用是一样的,区别仅仅在于LOCAL_CPPFLAGS只对C++源文件有效,而LOCAL_CFLAGS对C文件和C++文件全都有效。
LOCAL_LDFLAGS和LOCAL_CFLAGS类似,区别在于LOCAL_CFLAGS向编译器传递编译参数,而LOCAL_LDFLAGS向编译器传递链接参数。
LOCAL_LDLIBS用于链接系统内置的SO库文件。用法是去掉lib前缀加上-l,比如可以向下面这样链接libz.so和libjnigraphics.so:
LOCAL_LDLIBS := -lz -ljnigraphics
系统提供的全部内置API,可以查看官方文档:
https://developer.android.google.cn/ndk/guides/stable_apis
也可以看看ndk目录:
D:\android-ndk-r20b\platforms\android-16\arch-arm\usr\lib
头文件放在:
D:\android-ndk-r20b\sysroot\usr\include
如果你是windows系统,当你的Android.mk中LOCAL_SRC_FILES指定的源文件过多时,在调用ndk-build时会能会遇到一个CreateProcess fail的错误,原因是命令行参数的长度最多只能有8192个字符,遇到这种情况可以有以下解决办法:
- 在Android.mk中定义LOCAL_SHORT_COMMANDS := true
- 在Application.mk中定义APP_SHORT_COMMANDS := true
- 将一个大模块拆分为多个模块来编译
- 使用mac系统或者linux系统编译