深入浅出Android NDK之Android.mk常用C/C++编译选项

目录
上一篇 深入浅出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系统编译

下一篇 深入浅出Android NDK之Application.mk以及C++支持

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
这些都是 Android NDK 内部的 `Android.mk` 文件。其,`./android-ndk-r25c/sources/android/native_app_glue/Android.mk` 是用于编译 Native Activity 示例应用程序的 `Android.mk` 文件;`./android-ndk-r25c/sources/android/support/Android.mk` 是包含一些 Android 支持库的 `Android.mk` 文件;`./android-ndk-r25c/sources/android/ndk_helper/Android.mk` 是包含一些辅助函数和类的 `Android.mk` 文件;`./android-ndk-r25c/sources/android/cpufeatures/Android.mk` 是用于编译 `cpufeatures` 库的 `Android.mk` 文件,该库提供了一些 CPU 相关的信息和功能;`./android-ndk-r25c/sources/cxx-stl/llvm-libc++abi/Android.mk` 和 `./android-ndk-r25c/sources/cxx-stl/llvm-libc++/Android.mk` 是用于编译 C++ STL 库的 `Android.mk` 文件,分别对应 libc++abi 和 libc++ 两个 STL 库;`./android-ndk-r25c/sources/third_party/googletest/Android.mk` 是用于编译 Google Test 测试框架的 `Android.mk` 文件;`./android-ndk-r25c/sources/third_party/shaderc/Android.mk` 是用于编译 Shaderc 编译器的 `Android.mk` 文件,该编译器可以将 GLSL 代码编译成 SPIR-V 代码;`./android-ndk-r25c/sources/third_party/shaderc/libshaderc/Android.mk` 是用于编译 Shaderc 库的 `Android.mk` 文件;`./android-ndk-r25c/sources/third_party/shaderc/libshaderc_util/Android.mk` 是用于编译 Shaderc Util 库的 `Android.mk` 文件,该库提供了一些辅助函数和类;`./android-ndk-r25c/sources/third_party/shaderc/third_party/Android.mk` 是用于编译 Shaderc 编译器依赖的第三方库的 `Android.mk` 文件,包括 glslang 和 spirv-tools 两个库;`./android-ndk-r25c/sources/third_party/shaderc/third_party/glslang/Android.mk` 是用于编译 glslang 库的 `Android.mk` 文件;`./android-ndk-r25c/sources/third_party/shaderc/third_party/spirv-tools/Android.mk` 是用于编译 spirv-tools 库的 `Android.mk` 文件;`./android-ndk-r25c/sources/third_party/vulkan/src/build-android/jni/Android.mk` 是用于编译 Vulkan 库的 `Android.mk` 文件。 如果您要在 Android NDK 编写自己的 `Android.mk` 文件,可以参考这些示例文件。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值