【鸿蒙开发】第五十三章 NDK开发

目录

1 NDK开发导读

1.1 NDK适用场景

1.2 NDK必备基础知识

1.2.1 NDK基本概念

1.2.2 前置知识

1.2.3 NDK目录简介

1.3 NDK常用模块

 2 创建NDK工程

3.  构建NDK工程

3.1 NDK工程构建概述

3.1.1 ohos.toolchain.cmake简介

 3.2 使用DevEco Studio模板构建NDK工程

3.2.1 CMakeLists.txt

3.2.2 externalNativeOptions

3.3 使用命令行CMake构建NDK工程

3.3.1 下载NDK开发包

3.3.2 解压NDK开发包

3.3.3 配置环境变量

3.3.4 使用NDK开发包编译Native程序

1、demo工程内容

2、demo目录图

3、根目录CMakeLists.txt内容

 4、内部CMakeLists.txt内容

5、源码内容

3.3.5 编译构建demo工程

1、linux 和 mac 系统环境下

2、windows系统环境下

3.4 在NDK工程中使用预构建库

3.4.1 直接引入预构建库

3.4.2 预构建库的SONAME问题

3.4.3 使用远程依赖HAR中集成的预构建库

3.4.4 使用本地HAR中集成的预构建库

3.5 毕昇编译器

3.5.1 毕昇编译器简介

3.5.2 能力范围

3.5.3 亮点特征

3.5.4 毕昇编译器使用指导


1 NDK开发导读

NDK(Native Development Kit)是HarmonyOS SDK提供的Native API、相应编译脚本和编译工具链的集合,方便使用C或C++语言实现应用的关键功能。NDK只覆盖了HarmonyOS一些基础的底层能力,如C运行时基础库libc、图形库、窗口系统、多媒体、压缩库、面向ArkTS/JS与C跨语言的Node-API等,并没有提供ArkTS/JS API的完整能力。

运行态,可以使用NDK中的Node-API接口,访问、创建、操作JS对象;也允许JS对象使用Native动态库

1.1 NDK适用场景

适合使用NDK的场景:应用涉及如下场景时,适合采用NDK开发

  • 性能敏感的场景,如游戏、物理模拟等计算密集型场景。

  • 需要复用已有C或C++库的场景。

  • 需要针对CPU特性进行专项定制库的场景,如Neon加速。

不建议使用NDK的场景:应用涉及如下场景时,不建议采用NDK开发

  • 纯C或C++的应用。

  • 希望在尽可能多的HarmonyOS设备上保持兼容的应用。

1.2 NDK必备基础知识

为顺利进行NDK开发,开发者需要先掌握必要的基本概念及基础知识。

1.2.1 NDK基本概念

  • Node-API

    曾用名NAPI,是HarmonyOS中提供ArkTS/JS与C/C++跨语言调用的接口,是NDK接口中的一部分。该接口是在Node.js提供的Node-API基础上扩展而来,但与Node.js中的Node-API不完全兼容。

  • C API

    HarmonyOS NDK的曾用名,不再使用。

1.2.2 前置知识

  • Linux C语言编程知识

    内核、libc基础库基于POSIX等标准扩展而来,掌握基本的Linux C编程知识能够更好的帮助理解HarmonyOS NDK开发。

  • CMake使用知识

    CMake是HarmonyOS默认支持的构建系统。请先通过CMake官方文档了解基础用法。

  • Node Addons开发知识

    ArkTS采用Node-API作为跨语言调用接口,熟悉基本的Node Addons开发模式,可以更好理解NDK中Node-API的使用。

  • Clang/LLVM编译器使用知识

    具备一定的Clang/LLVM编译器基础知识,能够帮助开发者编译出更优的Native动态库。

1.2.3 NDK目录简介

  • build目录:放置预定义的toolchain脚本文件ohos.toolchain.cmake

    CMake编译时需要读取该文件中的默认值,比如编译器架构、C++库链接方式等,因此在编译时会通过CMAKE_TOOLCHAIN_FILE指出该文件的路径,便于CMake在编译时定位到该文件。

  • build-tools文件夹:放置NDK提供的编译工具

# 键入下一行命令查看CMake的版本
cmake -version
# 结果
cmake version 3.16.5
CMake suite maintained and supported by Kitware (kitware.com/cmake).
  • llvm文件夹:放置NDK提供的编译器

1.3 NDK常用模块

下表介绍了NDK的常用模块。

模块模块简介
标准C库以musl为基础提供的标准C库接口。
标准C++库C++运行时库libc++_shared。
日志打印日志到系统的HiLog接口。
Node-API当需要实现ArkTS/JS和C/C++之间的交互时,可以使用Node-API。
libuv三方异步IO库。
zlibzlib库,提供基本的数据压缩、解压接口。
Rawfile应用资源访问接口,可以读取应用中打包的各种资源。
XComponentArkUI XComponent组件提供surface与触屏事件等接口,方便开发者开发高性能图形应用。
Drawing系统提供的2D图形库,可以在surface进行绘制。
OpenGL系统提供的OpenGL 3D图形接口。
OpenSL ES用于2D、3D音频加速的接口库。

 2 创建NDK工程

  1. 通过如下两种方式,打开工程创建向导界面。

    • 如果当前未打开任何工程,可以在DevEco Studio的欢迎页,选择Create Project开始创建一个新NDK工程。
    • 如果已经打开了工程,可以在菜单栏选择File > New > Create Project来创建一个新NDK工程。
  2. 根据工程创建向导,选择Native C++工程模板,然后单击Next

  3. 在工程配置页面,根据向导配置工程的基本信息后,单击Finish,工具会自动生成示例代码和相关资源,等待工程创建完成。

    在工程entry/src/main目录下会包含cpp目录,该目录文件的详细介绍请参见C++工程目录结构

3.  构建NDK工程

3.1 NDK工程构建概述

HarmonyOS NDK默认使用CMake作为构建系统,随包提供了符合HarmonyOS工具链的基础配置文件ohos.toolchain.cmake,用于预定义CMake变量来简化开发者配置。

常用的NDK工程构建方式有:

3.1.1 ohos.toolchain.cmake简介

ohos.toolchain.cmake是HarmonyOS NDK提供给CMake的toolchain脚本,里面预定义了编译HarmonyOS应用需要设置的编译参数,如交叉编译设备的目标、C++运行时库的链接方式等;这些参数在调用CMake命令时,可以从命令行传入,来改变默认编译链接行为。此文件中的常用参数见下表。

参数类型说明
OHOS_STLc++_shared/c++_static

libc++的链接方式。默认为c++_shared。

c++_shared表示采用动态链接libc++_shared.so;c++_static表示采用静态链接libc++_static.a。

由于C++运行时中存在一些全局变量,因此同一应用中的全部Native库需要采用相同的链接方式。

OHOS_ARCHarmeabi-v7a/arm64-v8a/x86_64设置当前Native交叉编译的目标架构,当前支持的架构为armeabi-v7a/arm64-v8a/x86_64。
OHOS_PLATFORMOHOS选择平台。当前只支持HarmonyOS平台。

上述参数最终会控制Clang的交叉编译命令,产生合适的命令参数。

  • --target={arch}-linux-ohos参数,通知编译器生成相应架构下符合HarmonyOS ABI的二进制文件。

  • --sysroot={ndk_root}/sysroot参数,告知编译器HarmonyOS系统头文件的所在位置。

 3.2 使用DevEco Studio模板构建NDK工程

NDK通过CMakeNinja编译应用的C/C++代码,编译过程如下图所示。

核心编译过程如下:

  1. 根据CMake配置脚本以及build-profile.json5中配置的externalNativeOptions构建参数,与缓存中的配置比对后,生成CMake命令并执行CMake。

  2. 执行Ninja,按照makefile执行编译和链接,将生成的.so以及运行时依赖的.so同步到输出目录,完成构建过程。

通过DevEco Studio提供的应用模板,可以快速生成CMake构建脚本模板,并在build-profile.json5中指定相关编译构建参数。

3.2.1 CMakeLists.txt

通过DevEco Studio模板工程创建的NDK工程中,包含默认生成的CMakeLists.txt脚本,如下所示:

# the minimum version of CMake.
cmake_minimum_required(VERSION 3.4.1)
project(MyApplication) 

# 定义一个变量,并赋值为当前模块cpp目录
set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})

# 添加头文件.h目录,包括cpp,cpp/include,告诉cmake去这里找到代码引入的头文件
include_directories(${NATIVERENDER_ROOT_PATH}
                    ${NATIVERENDER_ROOT_PATH}/include)

# 声明一个产物libentry.so,SHARED表示产物为动态库,hello.cpp为产物的源代码
add_library(entry SHARED hello.cpp)

# 声明产物entry链接时需要的三方库libace_napi.z.so
# 这里直接写三方库的名称是因为它是在ndk中,已在链接寻址路径中,无需额外声明
target_link_libraries(entry PUBLIC libace_napi.z.so)

默认的CMakeLists.txt脚本中添加了编译所需的源代码、头文件以及三方库,可根据实际工程添加自定义编译参数、函数声明、简单的逻辑控制等。

3.2.2 externalNativeOptions

模块级build-profile.json5中externalNativeOptions参数是NDK工程C/C++文件编译配置的入口,可以通过path指定CMake脚本路径、arguments配置CMake参数、cppFlags配置C++编译器参数、abiFilters配置编译架构等。

"apiType": "stageMode",
"buildOption": {
  "arkOptions": {
   },
  "externalNativeOptions": {
    "path": "./src/main/cpp/CMakeLists.txt",
    "arguments": "",
    "cppFlags": "",
    "abiFilters": [
       "arm64-v8a",
       "x86_64"
    ],
  }
}

externalNativeOptions具体参数说明如下表所示。

配置项类型说明
pathstringCMake构建脚本地址,即CMakeLists.txt文件地址。
abiFiltersarray

本机的ABI编译环境,包括:

- arm64-v8a

- x86_64

如不配置该参数,编译时默认编译出arm64-v8a架构相关so。

argumentsstringCMake编译参数。
cppFlagsstringC++编译器参数。

3.3 使用命令行CMake构建NDK工程

3.3.1 下载NDK开发包

NDK开发相关工具位于$DevEco Studio安装目录/sdk/default/openharmony/native路径下。

3.3.2 解压NDK开发包

下载完成后,将压缩包放入创建好的文件夹下解压。

windows/linux 使用 SDK 包解压完成效果如下图所示:

mac使用 SDK 包解压完成效果如下图所示:

3.3.3 配置环境变量

如果只是在DevEco Studio中使用,跳过以下步骤:

  1. 将NDK自带的CMake编译工具添加到环境变量中
  • 配置 linux 系统下环境变量
# 打开.bashrc文件
vim ~/.bashrc
# 在文件最后添加cmake路径,该路径是自己的放置文件的路径,之后保存退出
export PATH=${实际SDK路径}/native/build-tools/cmake/bin:$PATH
# 在命令行执行source ~/.bashrc使环境变量生效
source ~/.bashrc
  •  配置 mac 系统下环境变量
#在当前用户目录下,打开 .bash_profile 文件,文件如果不存在,创建即可
vim ~/.bash_profile
#在文件最后添加 cmake 路径,该路径是自己的放置文件的路径,之后保存退出
export PATH=${实际SDK路径}/native/build-tools/cmake/bin:$PATH
#在命令行执行 source ~/.bash_profile 使环境变量生效
source ~/.bash_profile
  • 配置 windows 下的环境变量

右键点击我的电脑,在下拉框中选择我的电脑,点击高级系统设置,点击环境变量,点击Path后点编辑,点击新建,将路径添加进去,之后保存退出,打开cmd(若下一步不能够实现,请重启电脑尝试)。

打开命令框,输入{cmake实际安装路径}\cmake.exe -version,命令行正确回显cmake的版本号,

说明环境变量配置完成

  1. 查看CMake默认路径。
  • linux 和 mac 系统环境下
#在命令行输入which命令查询当前CMake所在路径
which cmake
#结果路径与.bashrc中设置一致
~/ohos-sdk/ohos-sdk/linux/native/build-tools/cmake/bin/cmake
  • windows 系统环境下,cmake 安装路径为自己所配置的环境变量路径

    通过 我的电脑->高级系统设置->环境变量->在 Path 对象中查看

3.3.4 使用NDK开发包编译Native程序

应用开发者可以通过NDK开发包快速的开发出Native动态库、静态库与可执行文件。NDK开发包提供CMake编译构建工具脚本,下面通过编写一个C/C++ demo工程来演示适配过程。

1、demo工程内容

下面是一个CMake的demo工程内容,此工程包含两个目录,include目录包含此库的头文件,src目录包含全部源码;src目录包含两个文件,sum.cpp的算法文件,以及main.cpp的调用算法的主入口文件,目标是编译成一个可执行程序,以及一个算法动态库。

2、demo目录图
  1. demo
  2. ├── CMakeLists.txt
  3. ├── include
  4. └── sum.h
  5. └── src
  6. ├── CMakeLists.txt
  7. ├── sum.cpp
  8. └── hello.cpp
3、根目录CMakeLists.txt内容
# 指定CMake的最小版本
CMAKE_MINIMUM_REQUIRED(VERSION 3.16)

# 工程名称,这里我们就叫HELLO
PROJECT(HELLO)

#添加一个子目录并构建该子目录。
ADD_SUBDIRECTORY(src)
 4、内部CMakeLists.txt内容
SET(LIBHELLO_SRC hello.cpp)

# 设置编译参数
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0")   
 
# 设置链接参数,具体参数可以忽略,纯粹为了举例
SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--emit-relocs --verbose")    

# 添加一个libsum动态库目标,编译成功会生成一个libsum.so
ADD_LIBRARY(sum SHARED sum.cpp)

# 生成可执行程序,添加一个Hello的可执行程序目标,编译成功会生成一个Hello可执行程序
ADD_EXECUTABLE(Hello ${LIBHELLO_SRC})

# 指定Hello目标include目录路径
TARGET_INCLUDE_DIRECTORIES(Hello PUBLIC ../include)

# 指定Hello目标需要链接的库名字
TARGET_LINK_LIBRARIES(Hello PUBLIC sum)
5、源码内容

hello.cpp源码

#include <iostream>
#include "sum.h"

int main(int argc,const char **argv)
{
    std::cout<< "hello world!" <<std::endl;
    int total = sum(1, 100);
    std::cout<< "Sum 1 + 100=" << total << std::endl;
    return 0;
}

sum.h源码

int sum(int a, int b);

sum.cpp源码

#include <iostream>
    
int sum(int a, int b)
{
    return a + b;
}

3.3.5 编译构建demo工程

1、linux 和 mac 系统环境下

在工程目录下,创建build目录,用来放置CMake构建时产生的中间文件。注意: ohos-sdk是下载下来的SDK的根目录,开发者需要自行替换成实际的下载目录。

1. 采用OHOS_STL=c++_shared动态链接c++库方式构建工程,如不指定,默认采用c++_shared;DOHOS_ARCH参数可根据系统架构来决定具体值。

 >mkdir build && cd build
 >cmake -DOHOS_STL=c++_shared -DOHOS_ARCH=armeabi-v7a -DOHOS_PLATFORM=OHOS -DCMAKE_TOOLCHAIN_FILE={ohos-sdk}/linux/native/build/cmake/ohos.toolchain.cmake ..
 >cmake --build .

 2. 采用OHOS_STL=c++_static静态链接c++库方式构建工程。

 >mkdir build && cd build
 >cmake -DOHOS_STL=c++_static -DOHOS_ARCH=armeabi-v7a -DOHOS_PLATFORM=OHOS -DCMAKE_TOOLCHAIN_FILE={ohos-sdk}/linux/native/build/cmake/ohos.toolchain.cmake ..
 >cmake --build .
  1. 命令中,OHOS_ARCH与OHOS_PLATFORM两个变量最终会生成clang++的--target命令参数,在此例子中就是--target=arm-linux-ohos --march=armv7a两个参数。

    CMAKE_TOOLCHAIN_FILE指定了toolchain文件,在此文件中默认给clang++设置了--sysroot={ndk_sysroot目录},告诉编译器查找系统头文件的根目录。

2、windows系统环境下

在windows下使用cmake进行编译,与linux下不同的是,使用cmake要加入参数 -G 选择使用的生成器,直接回车会列出下面的生成器。

这里使用的是cmake .. -G "Ninja" 引号里面跟的参数就是上图查看的环境所支持的生成器,这里ndk中自带的生成器是Ninja。

Step 1. 同样在工程目录下创建 build 文件夹并执行以下指令:

 F:\windows\native\build-tools\cmake\bin\cmake.exe -G "Ninja" -D OHOS_STL=c++_shared -D OHOS_ARCH=armeabi-v7a -D OHOS_PLATFORM=OHOS -D CMAKE_TOOLCHAIN_FILE=F:\windows\native\build\cmake\ohos.toolchain.cmake ..

注:如需debug调试,增加参数 -D CMAKE_BUILD_TYPE=normal;cmake路径和编译工具链ohos.toolchain.cmake路径都是下载好的ndk路径。

执行结果如下图:

这里生成的build.ninja文件就是我们需要的 。

Step 2. 让我们用ninja指令来编译生成目标文件,其位置如下图所示:

ninja -f build.ninja 或者用 cmake --build . 执行结果如下:

3.4 在NDK工程中使用预构建库

在NDK工程中,可以通过CMake语法规则引入并使用预构建库。在引用预构建库时,模块libs目录中的预构建库,以及在CMakeList.txt编译脚本中声明的预构建库都会被打包。

3.4.1 直接引入预构建库

可以通过直接将预构建的库文件复制到项目文件中, 来使用预构建库。例如在项目中需要使用预构建库libavcodec_ffmpeg.so,其开发态存放路径如下图所示:

在模块的CMakeLists.txt编译脚本中通过add_library添加所需的预构建库,并声明预构建库路径等信息后,可以在target_link_libraries中声明链接该预构建库,脚本示例如下所示:

add_library(library SHARED hello.cpp)

add_library(avcodec_ffmpeg SHARED IMPORTED)
set_target_properties(avcodec_ffmpeg
    PROPERTIES
    IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/third_party/FFmpeg/libs/${OHOS_ARCH}/libavcodec_ffmpeg.so)

target_link_libraries(library PUBLIC libace_napi.z.so avcodec_ffmpeg)

在模块的CMakeLists.txt编译脚本中添加include_directories:

include_directories(
    ...
    ${CMAKE_CURRENT_SOURCE_DIR}/third_party/FFmpeg/include
)

当在HAR中使用预构建库时,当前编译的库和链接所需预构建库会打包到HAR中的libs目录下,如下图所示:

3.4.2 预构建库的SONAME问题

请确保引入的预构建动态库(so)正确设置了SONAME

如果预构建so没有SONAME,链接器将会将so的绝对路径插入到依赖这个so的二进制文件的dynamic section中。当这些二进制文件随hap包发布运行时,动态加载器(dynamic loader)可能最终无法找到这个so而导致错误。

可以使用llvm-readelf工具查看so文件是否设置了SONAME。llvm-readelf工具路径为:${DevEco Studio安装目录}/sdk/default/openharmony/native/llvm/bin或者${command-line-tools安装目录}/sdk/default/openharmony/native/llvm/bin/llvm-readelf。

示例如下:

> ${YOUR_PATH}/command-line-tools/sdk/default/openharmony/native/llvm/bin/llvm-readelf -d libavcodec_ffmpeg.so | grep SONAME  
0x000000000000000e (SONAME)             Library soname: [libavcodec_ffmpeg.so]

若预构建so使用cmake进行构建,则所有的so默认会设置SONAME(只要目标平台支持)。

若预构建so使用其他构建工具,可以通过配置ldflags来设置。

${...}/clang++ ${...} -Wl,-soname,libavcodec_ffmpeg.so

3.4.3 使用远程依赖HAR中集成的预构建库

当使用远程依赖HAR中集成的预构建库时,CMakeLists.txt文件中引用脚本如下所示:

set(DEPENDENCY_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../../oh_modules)
add_library(library SHARED IMPORTED)
set_target_properties(library
    PROPERTIES
    IMPORTED_LOCATION ${DEPENDENCY_PATH}/library/libs/${OHOS_ARCH}/liblibrary.so)
add_library(entry SHARED hello.cpp)
target_link_libraries(entry PUBLIC libace_napi.z.so library)

3.4.4 使用本地HAR中集成的预构建库

当使用本地HAR中集成的预构建库时,CMakeLists.txt文件中引用脚本如下所示:

set(LIBRARY_DIR "${NATIVERENDER_ROOT_PATH}/../../../../library/build/default/intermediates/libs/default/${OHOS_ARCH}/")
add_library(library SHARED IMPORTED)
set_target_properties(library
    PROPERTIES
    IMPORTED_LOCATION ${LIBRARY_DIR}/liblibrary.so)
add_library(entry SHARED hello.cpp)
target_link_libraries(entry PUBLIC libace_napi.z.so)

3.5 毕昇编译器

3.5.1 毕昇编译器简介

毕昇编译器是基于LLVM开源软件开发的一款用于C/C++等语言的native编译器,能将C/C++代码工程编译链接成可以在设备上运行的二进制。在无需改动用户代码的条件下,相比业界主流的开源LLVM或GCC编译器,毕昇编译器能提供更强大的优化能力,使编译链接出来的二进制的运行时长更短、指令数更少,帮助提升应用在设备上的运行流畅度。

3.5.2 能力范围

毕昇编译器提供将C/C++代码工程编译链接成可以在设备上运行的二进制的基本能力,主要包括以下三方面:

  • 编译能力:将C/C++源码文件编译成汇编文件,汇编文件是指使用汇编语言编写的文件。
  • 汇编能力:将汇编文件汇编成可重定向文件,可重定向文件是ELF格式的二进制文件,但不能直接放在设备上运行。
  • 链接能力:将一个或多个可重定向文件一起链接成一个可执行的二进制文件。

3.5.3 亮点特征

毕昇编译器相对于LLVM/GCC编译器有以下特点。

  • 循环优化增强

针对循环相关的编译优化,毕昇编译器在场景识别、结构变换等方面做了改进和增强。例如在社区LLVM已有的Loop Distribution优化上,毕昇编译器相比开源LLVM编译器,能额外识别出循环内不同代码块间数据依赖关系、以及不同代码块运行的迭代次数差别,从而能对更多的循环进行loop distribution优化。

Figure 1 毕昇编译器Loop Distribution优化增强示例

  • 矢量化优化增强

毕昇编译器在矢量化优化方面,相比开源LLVM编译器,不仅能将更多的循环做矢量化转换,还在矢量化指令选择上更高效。例如下面示例中,开源LLVM编译器虽然做了矢量化,但使用了5条矢量指令;而毕昇编译器只需要使用2条矢量指令,最终产生的二进制效率更优。

Figure 2 毕昇编译器矢量化优化增强示例

3.5.4 毕昇编译器使用指导

在DevEco Studio 中使用毕昇编译器:

获取DevEco Studio 5.0.3.402及以上的版本,在HarmonyOS应用的工程级build-profile.json5中简单配置即可使用毕昇编译器:在runtimeOS为HarmonyOS的时候,设置nativeCompilerBiSheng,即可使用毕昇编译器构建HarmonyOS工程的C/C++代码。

后续详见开发者文档:代码开发-NDK开发 - 华为HarmonyOS开发者 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值