想了解更多关于开源的内容,请访问:
51CTO 开源基础软件社区
https://ost.51cto.com
在OpenHarmony应用开发中选择Native C ++开发方式需要使用OpenHarmony NDK工具,或者想移植一个C/C ++的三方库到OpenHarmony中也可以使用NDK。NDK使用到的编译工具是cmake,也就是CMakeLists.txt那一套。Native C ++应用开发方式可以做直接编译三方库的so库再利用NAPI框架实现能力的调用(只需要在三方库原生的CMakeLists.txt中修改小部分的内容就可以实现)。
一、OpenHarmony NDK
- NDK (原生开发套件) 是一套工具,使开发者能够在 OpenHarmony hap应用中使用 C/C++ 代码。
- NDK提供了一系列的工具可以帮助开发者快速的开发C/C++的动态库、静态库和可执行文件。
- OpenHarmony 应用开发的Native C++开发方式就要依赖NDK。NDK被包含在OpenHarmony SDK中。可以在DevEco Studio使用 NDK 将 C/C ++ 代码编译到so库中,然后使用 DevEco Studio 的构建插件hvigor-ohos-plugin将so库打包到 Hap 中。ArkTS代码随后可以通过NAPI框架调用SO库中的函数。
二、获取NDK的方式
1、从每日构建中获取
每日构建地址: http://ci.openharmony.cn/dailys/dailybuilds。
组件形态选择ohos-sdk,版本选择最新版本的sdk一般是没有问题的(但是笔者之前遇到下载的ndk中的clang工具找不到libatomic.so无法工作的情况的情况)。
下载解压后可以得到如下文件,根据linux或者windows端解压不同文件得到sdk。
2、编译源码得到SDK中的NDK
安装依赖
./build/build_scripts/env_setup.sh
执行完上述命令后记得执行source ~/.bashrc或者重启终端
source ~/.bashrc
安装编译SDK需要的依赖包(编译镜像的时候是不依赖这些包的)
sudo apt-get install libxcursor-dev libxrandr-dev libxinerama-dev
./build.sh --product-name ohos-sdk --ccache --build-target ohos_ndk
写文章提的issue https://gitee.com/openharmony/build/issues/I6H8IO?from=project-issue
- 在对应的目录底下找到编译成功的NDK,相关路径out/sdk/packages/ohos-sdk/linux/native,根据linux或者windows端解压不同文件得到sdk
二、NDK目录
native
├── NOTICE.txt 声明文件
├── build
│ └── cmake
│ ├── ohos.toolchain.cmake ---->编译的工具链
│ └── sdk_native_platforms.cmake ---->编译的工具链(在DevEco Studio中编译打包so要用到)
├── build-tools ---->cmake编译工具所在目录(NDK提供的编译工具)
├── llvm ---->编译器所在目录(NDK提供的编译工具)
├── sysroot ---->编译器的 sysroot 目录,存放 SDK 内部的已经包含的库和对应的头文件
├── ndk_system_capability.json ---->NDK自带so库能力描述文件
├── nativeapi_syscap_config.json ---->调用NDK自带so库能力相关头文件
├── docs ---->调用NDK自带so库能力相关文档
└── oh-uni-package.json ---->SDK 信息描述
FileCheck clang-format ld64.lld lldb-vscode llvm-cxxfilt llvm-objdump llvm-strip yaml2obj
clang clang-tidy lld llvm-addr2line llvm-dis llvm-profdata llvm-symbolizer
clang++ clangd lld-link llvm-ar llvm-lib llvm-ranlib not
clang-12 count lldb llvm-as llvm-link llvm-readelf sancov
clang-check dsymutil lldb-argdumper llvm-cfi-verify llvm-modextract llvm-readobj sanstats
clang-cl git-clang-format lldb-mi llvm-config llvm-nm llvm-size scan-build
clang-cpp ld.lld lldb-server llvm-cov llvm-objcopy llvm-strings scan-view
三、linux下使用NDK编译库文件和可执行文件
1、使用NDK编译一个简单demo
shared-library
├── CMakeLists.txt 外部CMakeLists.txt
├── include 头文件目录
│ └── shared
│ └── Hello.h
└── src 源文件目录
├── CMakeLists.txt 内部CMakeLists.txt
├── Hello.cpp
└── main.cpp
#cmake的版本
CMAKE_MINIMUM_REQUIRED(VERSION 3.16)
#工程名称
PROJECT(HELLO_LIBRARY)
#添加一个子目录并构建该子目录
ADD_SUBDIRECTORY(src)
cmake的内置命令是不区分大小写的,因此add_subdirectory与ADD_SUBDIRECTORY作用一致。但是cmake的所有变量都是区分大小写的
#设置可执行文件输出路径
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/output)
# 设置so库文件输出路径
SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/output)
############################################################
# Create a library
############################################################
#SHARED表示生成的是动态库,动态库的名字是hello_shared_library。生成动态库的源文件是Hello.cpp
ADD_LIBRARY(hello_shared_library SHARED
Hello.cpp
)
#为生成的动态库添加一个别名,后续hello::library可使用来替代hello_shared_library
ADD_LIBRARY(hello::library ALIAS hello_shared_library)
#为指定目标hello_shared_library添加头文件搜索路径(这个指定目标决不能是alias target,也就是指定的别名hello::library)
TARGET_INCLUDE_DIRECTORIES(hello_shared_library
PUBLIC
${PROJECT_SOURCE_DIR}/include
)
############################################################
# Create an executable
############################################################
# 使用指定的源文件main.cpp创建出一个可执行文件hello_shared_binary
ADD_EXECUTABLE(hello_shared_binary
main.cpp
)
#TARGET_LINK_LIBRARIES指令的作用为将目标文件与库文件进行链接
#将可执行文件hello_shared_binary与库文件hello::library进行链接
TARGET_LINK_LIBRARIES( hello_shared_binary
PRIVATE
hello::library
)
3、进行cmake编译
(1)NDK自带的cmake编译工具添加到环境变量
1、第一种方法:
#将其临时加入环境变量中(适用于临时想用用NDK)
export PATH=$PATH:/ohos-sdk/linux/native/build-tools/cmake/bin(你自己解压的NDK目录)
2、第二种方法:
#将其永远加入环境变量中
#打开.bashrc文件
vim ~/.bashrc
#在文件最后添加cmake路径,该路径是自己的放置文件的路径,之后保存退出
export PATH=/ohos-sdk/linux/native/build-tools/cmake/bin:$PATH
#在命令行执行source ~/.bashrc使环境变量生效
source ~/.bashrc
3、查看环境变量添加是否成功。
可知cmake的版本是3.16.5 ,所以编写CMakeLists.txt可参考的链接为 https://cmake.org/cmake/help/v3.16/guide/tutorial/
(2)安装make
如果没有安装make会出现以下报错,则执行sudo apt-get install make。(如果已经安装make,请忽略)。
(3)cmake的几个参数
1、OHOS_STL参数,其类型可以是c++_shared和c++_static,默认是c++_shared。
2、OHOS_ARCH参数(设置应用程序二进制接口ABI),其类型是armeabi-v7a、x86_64 ,默认值是arm64-v8a。每一种ABI定义了相应的二进制文件。
- armeabi-v7a (选择这个是编译32位的二进制文件)。
- arm64-v8a (选择这个是编译64位的二进制文件)。
- x86_64 (现在不使用,OpenHarmony现在在适配x86的芯片)。
3、OHOS_PLATFORM参数,其类型为OHOS(只能选择OHOS平台)。
4、 CMAKE_TOOLCHAIN_FILE参数指向的是工具链文件所在的位置,就是ohos.toolchain.cmake的路径(对于编译不同平台二进制文件是很重要的)。
- build目录中的工具链文件ohos.toolchain.cmake,cmake编译时需要读取该文件中的默认值,比如编译器的选择、编译平台,例如ohos.toolchain.cmake文件中,
# Common default settings
39:if(NOT DEFINED OHOS_PLATFORM_LEVEL)
set(OHOS_PLATFORM_LEVEL 1)
endif()
43:if(NOT DEFINED OHOS_TOOLCHAIN)
set(OHOS_TOOLCHAIN clang)
endif()
47:if(NOT DEFINED OHOS_STL)
set(OHOS_STL c++_shared)
endif()
51:if(NOT DEFINED OHOS_PIE)
set(OHOS_PIE TRUE)
endif()
55:if(NOT DEFINED OHOS_ARM_NEON)
set(OHOS_ARM_NEON thumb)
endif()
60:if(NOT DEFINED OHOS_ARCH)
set(OHOS_ARCH arm64-v8a)
endif()
- 在编译时需要指出该文件的所在路径,以便于cmake在编译时定位到该文件。在编译的时候需要为cmake指定参数来控制编译目标的属性。
(4)使用CMake进行构建,并传递工具链文件及cmake参数
# dmeo目录下创建build目录,用来放置cmake构建时产生的中间文件。
mkdir build && cd build
# 传递OHOS_STL(可选,默认就是c++_shared)、OHOS_ARCH、OHOS_PLATFORM、工具链文件
# CMAKE_TOOLCHAIN_FILE是ohos.toolchain.cmake具体放置的路径
# 这一步会检查CMakeLists.txt是否有语法错误
# 不要忘记在ohos.toolchain.cmake路径后面加上 ..
cmake -D OHOS_STL=c++_shared -D OHOS_ARCH=armeabi-v7a -D OHOS_PLATFORM=OHOS -D CMAKE_TOOLCHAIN_FILE=/xxx/build/cmake/ohos.toolchain.cmake ..
cmake --build .
1、执行如下过程如下:
2、可以注意到执行cmake --build .时会有warning: -Wunused-command-line-argument,通过在内部的CMakeLists.txt中添加如下语句消除warning。
set(CMAKE_CXX_FLAGS "-Wno-unused-command-line-argument")
- 注意:.c文件时是使用CMAKE_C_FLAGS,.cpp文件时是使用CMAKE_CXX_FLAGS。
3、编译结果如下:
生成可执行文件和动态库。
4、要编译静态库及其可执行文件,内部的CMakeLists.txt中添加如下语句
############################################################
# Create a library
############################################################
#Generate the static library from the library sources
ADD_LIBRARY(hello_static_library STATIC
Hello.cpp
)
TARGET_INCLUDE_DIRECTORIES(hello_static_library
PUBLIC
${PROJECT_SOURCE_DIR}/include
)
############################################################
# Create an executable
############################################################
# Add an executable with the above sources
ADD_EXECUTABLE(hello_static_binary
main.cpp
)
# link the new hello_library target with the hello_binary target
TARGET_LINK_LIBRARIES( hello_static_binary
PRIVATE
hello_static_library
)
编译结果如下,生成静态库及其可执行文件。
5、编译64位的库文件和可执行文件,OHOS_ARCH参数选择arm64-v8a或者编译时不传递该参数。
(5)使用CMake进行构建,选择ninja生成器并生成编译流程图
1、执行cmake -G查看linux环境下的生成器,ndk中自带的生成器是Ninja。
2、使用CMake进行构建时添加上-G “Ninja”。
第一步:mkdir build && cd build
第二步:cmake -G "Ninja" -D OHOS_STL=c++_shared -D OHOS_ARCH=armeabi-v7a -D OHOS_PLATFORM=OHOS -D CMAKE_TOOLCHAIN_FILE=/xxx/build/cmake/ohos.toolchain.cmake ..
第三步:cmake --build .或者ninja -f build.ninja
执行完第二步后,build目录下会生成build.ninja,有了build.ninja可以使用ninja生成程序的编译流程图。
3、使用ninja工具生成库文件和可执行文件的编译流程图。
# 将libhello_shared_library.so动态库的编译流程转为dot
ninja -t graph libhello_shared_library.so > xxx.dot
# 将dot格式转化为png格式的流程图
dot -T png xxx.dot -o libhello_shared_library.so.png
# 将dot格式转化为svg格式的流程图
dot -T svg xxx.dot -o libhello_shared_library.so.svg
如果生成图片格式太小的话,可以生成svg格式到浏览器打开,这一点很重要,请参考该 issue。
(6)使用NDK中的cmake-gui进行图形化操作编译
1、native/build-tools/cmake/bin目录有cmake-gui,可以用它图形化传入编译参数进行编译。
2、首先点击Where is source code行的Browser Source,加载工程所在目录。再点击Where to build the binarys行的Browser Source,加载点击“Where is source code”行的Browser Source,加载源码所在目录。点击“Where to build the binarys行的Browser Source,加载工程所在目录下的build目录(没有就创建一个)。
3、点击Add Entry传入cmake 参数。勾选把旁边的Grouped选项。
4、点击Configure选择Current Generator生成器为Unix Makefiles。
5、最后打开Where to build the binarys加载构建目录终端,执行make -j 8命令。
make -j n含义是 让make最多允许n个编译命令同时执行,这样可以更有效的利用CPU资源。假设系统cpu是12核,在不影响其他工作的情况下,我们可以make -j 12将cpu资源充分利用起来,一般来说,最大并行任务数为cpu_num * 2。
想了解更多关于开源的内容,请访问:
51CTO 开源基础软件社区
https://ost.51cto.com