Android NDK开发详解之编写C/C++代码中的C++ 库支持

本部分将讨论如何使用 NDK 提供的库。
注意:有关导入预构建库(未包含在 NDK 中的库)的指南已移至各个构建系统的相关部分。请根据您的项目需求参阅 CMake 或 ndk-build 指南。

文中说明了 NDK 提供的 C ++ 运行时,并介绍了 NDK 提供的其他库(例如 OpenGL ES 和 OpenSL ES)以及支持这些库所需的最低 Android API 级别。

C++ 运行时库

NDK 支持多种 C++ 运行时库。本文档介绍了这些库的相关信息、所涉及的折衷做法以及库的使用方法。

表 1. NDK C++ 运行时和功能。
在这里插入图片描述
警告:使用静态运行时可能会导致意外行为。如需了解详情,请参阅静态运行时部分。

libc++

LLVM 的 libc++ 是 C++ 标准库,自 Lollipop 以来 Android 操作系统便一直使用该库,并且从 NDK r18 开始成为 NDK 中唯一可用的 STL。

注意:如需全面详细了解任何给定版本对 C++ 库的支持级别是否达到了预期,请参阅 C++14 Status、C++17 Status 和 C++20 Status 页面(C++20 以前称为 C++2a)。编译器的 C++ 语言支持级别互不相关;请参阅 Clang 中的 C++ 支持。
CMake 默认设置为 C++ Clang 默认设置的版本(目前为 C++14),因此您需要将 CMakeLists.txt 中的标准 CMAKE_CXX_STANDARD 设置为适当的值,才能使用 C++17 或更高版本的功能。如需了解详情,请参阅 CMake 中介绍 CMAKE_CXX_STANDARD 的文档。

ndk-build 默认情况下仍将此决定留给 Clang,因此 ndk-build 用户应使用 APP_CPPFLAGS 来添加 -std=c++17 或任何所需内容。

libc++ 的共享库为 libc++_shared.so,静态库为 libc++_static.a。通常情况下,构建系统将根据用户需要对这些库的使用和打包进行处理。如果是非典型情况或在您实现自己的构建系统时,请参阅构建系统维护者指南或使用其他构建系统的指南。

注意:libc++ 不是系统库。如果您使用 libc++_shared.so,就必须将该库添加到您的应用中。如果您使用 Gradle 构建应用,此步骤会自动完成。
LLVM 项目受 Apache 许可 v2.0 约束,LLVM 除外。如需了解详情,请参阅许可文件。

system

system 运行时指的是 /system/lib/libstdc++.so。请勿将该库与 GNU 的全功能 libstdc++ 混淆。在 Android 系统中,libstdc++ 只是 new 和 delete。对于全功能 C++ 标准库,请使用 libc++。

注意:系统 STL 将在未来 NDK 版本中移除。请参阅问题 744。
系统 C++ 运行时支持基础 C++ 运行时 ABI。从根本上来说,此库就是提供 new 和 delete。不同于 NDK 中提供的其他选项,此库不支持异常处理和 RTTI。

除 等用于 C 库头文件的 C++ 封装容器之外,并无标准库支持。如需 STL,您应使用本页面提供的其他选项。

none

另外,您还可选择不使用 STL。在这种情况下,没有关联或授权要求。不提供 C++ 标准头文件。

选择 C++ 运行时

CMake
CMake 的默认值为 c++_static。

您可以使用模块级 build.gradle 文件中的 ANDROID_STL 变量指定 c++_shared、c++_static、none 或 system。如需了解详情,请参阅有关 CMake 中 ANDROID_STL 的文档。

ndk-build
ndk-build 的默认值为 none。

您可以使用 Application.mk 文件中的 APP_STL 变量指定 c++_shared、c++_static、none 或 system。例如:

APP_STL := c++_shared
ndk-build 仅允许为您的应用选择一个运行时,并且只能在 Application.mk 中进行选择。

直接使用 clang
如果您在自己的构建系统中直接使用 clang,则 clang++ 将默认使用 c++_shared。如需使用静态变体,请将 -static-libstdc++ 添加至链接器标志中。请注意,尽管由于历史原因该选项使用的名称是“libstdc++”,但也适用于 libc++。

重要注意事项

静态运行时

如果应用的所有原生代码均位于一个共享库中,我们建议使用静态运行时。这样可让链接器最大限度内联和精简未使用的代码,使应用达到最优化状态且文件最小巧。这样做还能避免旧版 Android 中的 PackageManager 和动态链接器出现错误,此类错误可导致处理多个共享库变得困难,且容易出错。

然而,在 C++ 中,在单一程序中定义多个相同函数或对象的副本并不安全。这是 C++ 标准中提出的单一定义规则的一个方面。

如果使用静态运行时(以及一般静态库),很容易在不经意间破坏这条规则。例如,以下应用就破坏了这一规则:

# Application.mk
APP_STL := c++_static

# Android.mk

include $(CLEAR_VARS)
LOCAL_MODULE := foo
LOCAL_SRC_FILES := foo.cpp
include $(BUILD_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := bar
LOCAL_SRC_FILES := bar.cpp
LOCAL_SHARED_LIBRARIES := foo
include $(BUILD_SHARED_LIBRARY)

在这种情况下,包括全局数据和静态构造函数在内的 STL 将同时存在于两个库中。此应用的运行时行为未定义,因此在实际运行过程中,应用会经常崩溃。其他可能存在的问题包括:

内存在一个库中分配,而在另一个库中释放,从而导致内存泄漏或堆损坏。
libfoo.so 中引发的异常在 libbar.so 中未被捕获,从而导致应用崩溃。
std::cout 的缓冲未正常运行。
将静态运行时链接至多个库,除了会导致行为问题,还会在每个共享库中复制代码,从而增加应用的大小。

一般情况下,只有在应用中有且只有一个共享库时,才能使用 C++ 运行时的静态变体。

注意:此规则既适用于您的代码,也适用于您的第三方依赖项。

共享运行时

注意:通过 Java AAR 分发的 JNI 库不得使用共享运行时,以免与其他库和应用发生冲突。以下警告仍然适用。如需了解详情,请参阅中间件供应商文档。
如果应用包括多个共享库,应使用 libc++_shared.so。

在 Android 系统中,NDK 使用的 libc++ 与操作系统的 libc++ 是不同的。如此一来,即使应用以旧版 Android 为目标平台,NDK 用户也能获得最新的 libc++ 功能和 bug 修复。需要权衡的是,如果使用 libc++_shared.so,就必须将该库添加到您的应用中。如果使用 Gradle 构建应用,此步骤会自动完成。

旧版 Android 的 PackageManager 和动态链接器存在 bug,导致原生库的安装、更新和加载不可靠。具体而言,如果应用以早于 Android 4.3(Android API 级别 18)的 Android 版本为目标,并且您使用 libc++_shared.so,必须先加载共享库,再加载依赖于共享库的其他库。

ReLinker 项目能够解决所有已知的原生库加载问题,而且相较于自行编写解决方法,它通常是更好的选择。

每个应用一个 STL

过去,NDK 除了支持 libc++,还支持 GNU libstdc++ 和 STLport。如果应用依赖于预构建库,而构建相应库所使用的 NDK 与构建应用使用的 NDK 不同,需确保其兼容性。

一个应用不得使用多个 C++ 运行时。不同的 STL 互不兼容。举例来说,libc++ 中 std::string 的布局不同于 gnustl。根据某种 STL 编写的代码无法使用以另一种 STL 编写的对象。以上仅举一例,其他不兼容情况不胜枚举。

注意:此规则的例外情况是,“无 STL”不算 STL。在同一应用中,您可放心地将仅有 C 语言的库(甚或 none 或 system 运行时,因为它们并非实际意义上的 STL)作为 STL 使用。此规则只适用于 libc++、gnustl 和 stlport。
警告:在构建阶段,链接器可以捕获其中一些问题,但还有许多这类问题只会在运行阶段显现,表现为崩溃或异常行为。
此规则不仅仅适用于您的代码。您的所有依赖项也必须使用与您所选 STL 相同的 STL。如果您使用闭源第三方依赖项,而该依赖项使用 STL,且不能为每个 STL 分别提供一个库,那么您就无法选择 STL。您必须使用与依赖项相同的 STL。

您有可能依赖两个互不兼容的库。在这种情况下,您只能弃用其中一个依赖项,或请求维护者提供根据另一个 STL 构建的库。

注意:虽然我们努力保持 NDK 各个版本 ABI 的兼容性,但这并非总能实现。为了获得最佳的兼容性,您除了要使用与依赖项相同的 STL,还需尽可能使用相同版本的 NDK。

C++ 异常

C++ 异常受 libc++ 支持,但其在 ndk-build 中默认为停用状态。这是因为之前 NDK 并不支持 C++ 异常。CMake 和独立工具链默认启用 C++ 异常。

若要在 ndk-build 中针对整个应用启用异常,请将下面这一行代码添加至 Application.mk 文件:

APP_CPPFLAGS := -fexceptions

若要针对单一 ndk-build 模块启用异常,请将下面这一行代码添加至相应模块的 Android.mk 中:

LOCAL_CPP_FEATURES := exceptions

或者,您可以使用:

LOCAL_CPPFLAGS := -fexceptions

RTTI

与异常一样,RTTI 也受 libc++ 支持,但在 ndk-build 中默认为停用状态。CMake 和独立工具链默认启用 RTTI。

若要在 ndk-build 中针对整个应用启用 RTTI,请将下面这一行代码添加至 Application.mk 文件:

APP_CPPFLAGS := -frtti

若要针对单一 ndk-build 模块启用 RTTI,请将下面这行代码添加至相应模块的 Android.mk 中:

LOCAL_CPP_FEATURES := rtti

或者,您可以使用:

LOCAL_CPPFLAGS := -frtti

本页面上的内容和代码示例受内容许可部分所述许可的限制。Java 和 OpenJDK 是 Oracle 和/或其关联公司的注册商标。

最后更新时间 (UTC):2022-02-07。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

五一编程

程序之路有我与你同行

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值