HarmonyOS鸿蒙Next中使用NDK开发基本流程和基础DEMO

HarmonyOS鸿蒙Next中使用NDK开发基本流程和基础DEMO 1、NDK适用场景

  • 性能敏感的场景,如游戏、物理模拟等计算密集型场景
  • 需要复用已有C或C++库的场景
  • 需要针对CPU特性进行专项定制库的场景,如Neon加速
  • 纯C或C++的应用不建议使用NDK
  • 希望在尽可能多的HarmonyOS设备上保持兼容的应用不建议使用NDK

2、NDK的基本概念

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

3、依赖知识模块

  • Linux C语言编程知识
  • CMake使用知识
  • Node Addons开发知识
  • Clang/LLVM编译器使用知识

4、NDK工程

1、创建NDK工程

根据工程创建向导,选择Native C++工程模板,然后单机Next。

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

2、构建NDK工程

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

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)

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具体参数说明如下表所示。

配置项 类型 说明
path string CMake构建脚本地址,即CMakeLists.txt文件地址。
abiFilters array 本机的ABI编译环境,包括: - arm64-v8a - x86_64 如不配置该参数,编译时默认编译出arm64-v8a架构相关so。
arguments string CMake编译参数。
cppFlags string C++编译器参数。

5、DEMO

我们代码开发主要会涉及到三个文件:

  • index.d.ts 从C/C++侧导出到ArkTS端的接口声明文件
  • napi_init.cpp c++侧具体的代码实现
  • index.ets ArkTS侧代码

在C++源码侧新增一个函数

#include "napi/native_api.h"

// 模板自动生成的代码
static napi_value Add(napi_env env, napi_callback_info info)
{
    size_t argc = 2;
    napi_value args[2] = {nullptr};

    napi_get_cb_info(env, info, &argc, args , nullptr, nullptr);

    napi_valuetype valuetype0;
    napi_typeof(env, args[0], &valuetype0);

    napi_valuetype valuetype1;
    napi_typeof(env, args[1], &valuetype1);

    double value0;
    napi_get_value_double(env, args[0], &value0);

    double value1;
    napi_get_value_double(env, args[1], &value1);

    napi_value sum;
    napi_create_double(env, value0 + value1, &sum);

    return sum;
}

// 添加一个Minus函数
static napi_value Minus(napi_env env, napi_callback_info info)
{
    size_t argc = 2;
    napi_value args[2] = {nullptr};

    napi_get_cb_info(env, info, &argc, args , nullptr, nullptr);

    napi_valuetype valuetype0;
    napi_typeof(env, args[0], &valuetype0);

    napi_valuetype valuetype1;
    napi_typeof(env, args[1], &valuetype1);

    double value0;
    napi_get_value_double(env, args[0], &value0);

    double value1;
    napi_get_value_double(env, args[1], &value1);

    napi_value sum;
    napi_create_double(env, value0 - value1, &sum);

    return sum;
}

EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports)
{
    napi_property_descriptor desc[] = {
        { "add", nullptr, Add, nullptr, nullptr, nullptr, napi_default, nullptr },
        // 将Minus导出,导出的函数名为minus
        { "minus", nullptr, Minus, nullptr, nullptr, nullptr, napi_default, nullptr }
    };
    napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
    return exports;
}
EXTERN_C_END

static napi_module demoModule = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = Init,
    .nm_modname = "entry",
    .nm_priv = ((void*)0),
    .reserved = { 0 },
};

extern "C" __attribute__((constructor)) void RegisterEntryModule(void)
{
    napi_module_register(&demoModule);
}

在index.d.ts添加增加的函数声明

export const add: (a: number, b: number) => number;
// 新增一个minus函数导出到ArkTS侧
export const minus: (a: number, b: number) => number;

在ArkTS侧使用C++侧导出的接口

import { hilog } from '@kit.PerformanceAnalysisKit';
import testNapi from 'libentry.so'; // 引用C++模块

@Entry
@Component
struct Index {
  @State message: string = 'Hello World';

  aboutToAppear(): void {
  	// 调用C++模块的函数接口
    hilog.info(0x0000, 'testTag', 'Test NAPI 2 + 3 = %{public}d', testNapi.add(2, 3));
    hilog.info(0x0000, 'testTag', 'Test NAPI 12 - 3 = %{public}d', testNapi.minus(12, 3));
  }

  build() {
    Row() {
      Column() {
        Text(this.message)
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
      }
      .width('100%')
    }
    .height('100%')
  }
}

C++模块包名的指定:

// src/main/cpp/types/libentry/oh-package.json5
{
    "name": "libentry.so",
    "types": "./Index.d.ts",
    "version": "1.0.0",
    "description": ""
}

更多关于HarmonyOS鸿蒙Next中使用NDK开发基本流程和基础DEMO的实战教程也可以访问 https://www.itying.com/category-93-b0.html

4 回复

依赖知识模块

  • Linux C语言编程知识
  • CMake使用知识
  • Node Addons开发知识
  • Clang/LLVM编译器使用知识

更多关于HarmonyOS鸿蒙Next中使用NDK开发基本流程和基础DEMO的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


《使用NDK开发基本流程和基础DEMO》

不错不错。

在HarmonyOS鸿蒙Next中使用NDK(Native Development Kit)开发的基本流程如下:

  1. 环境准备:确保已安装DevEco Studio和NDK工具链。NDK工具链包括编译器、调试器和库文件,用于编译C/C++代码。

  2. 创建项目:在DevEco Studio中创建一个新的HarmonyOS项目,选择Native C++模板。这将自动生成一个包含C++代码的项目结构。

  3. 配置NDK:在项目的build.gradle文件中配置NDK路径和编译选项。例如,指定NDK版本、ABI(应用二进制接口)和目标平台。

  4. 编写C/C++代码:在cpp目录下编写C/C++代码。可以使用JNI(Java Native Interface)与Java/Kotlin代码进行交互。

  5. 编译和构建:使用DevEco Studio的构建工具编译C/C++代码,生成共享库(.so文件)。构建过程会自动处理依赖关系和链接库。

  6. 集成到应用:将生成的共享库集成到HarmonyOS应用中。在Java/Kotlin代码中通过System.loadLibrary加载共享库,并调用其中的原生方法。

  7. 调试和测试:使用DevEco Studio的调试工具对原生代码进行调试。可以设置断点、查看变量值和调用栈。

基础DEMO示例:

  1. 创建Native C++项目:在DevEco Studio中创建新项目,选择Native C++模板。

  2. 编写C++代码:在cpp目录下创建native-lib.cpp文件,编写一个简单的C++函数,例如返回字符串。

#include <jni.h>
#include <string>

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_myapplication_MainActivity_stringFromJNI(JNIEnv* env, jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}
  1. 配置NDK:在build.gradle中配置NDK路径和编译选项。
android {
    ...
    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }
    ndkVersion "21.3.6528147"
}
  1. 编译和构建:点击Build按钮,编译C++代码并生成共享库。

  2. 集成到应用:在MainActivity.java中加载共享库并调用原生方法。

public class MainActivity extends AppCompatActivity {
    static {
        System.loadLibrary("native-lib");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        TextView tv = findViewById(R.id.sample_text);
        tv.setText(stringFromJNI());
    }

    public native String stringFromJNI();
}
  1. 运行和测试:运行应用,查看TextView中显示的字符串。

以上是HarmonyOS鸿蒙Next中使用NDK开发的基本流程和基础DEMO。

在HarmonyOS鸿蒙Next中使用NDK开发的基本流程如下:

  1. 环境配置:确保已安装DevEco Studio和NDK工具链。
  2. 创建项目:在DevEco Studio中创建Native C++项目。
  3. 编写C/C++代码:在src/main/cpp目录下编写Native代码。
  4. 配置CMake:在CMakeLists.txt中配置编译选项和链接库。
  5. Java/Kotlin调用:通过JNI接口在Java/Kotlin中调用Native方法。
  6. 编译运行:编译项目并在设备或模拟器上运行。

基础DEMO示例:

// native-lib.cpp
#include <jni.h>
#include <string>

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_myapp_MainActivity_stringFromJNI(JNIEnv* env, jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}
// MainActivity.java
public class MainActivity extends AppCompatActivity {
    static {
        System.loadLibrary("native-lib");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView tv = findViewById(R.id.sample_text);
        tv.setText(stringFromJNI());
    }

    public native String stringFromJNI();
}

通过以上步骤,你可以在HarmonyOS中实现基本的NDK开发。

回到顶部