OpenHarmony 入门——初识JS/ArkTS 侧的“JNI” NAPI基本开发步骤(三)

news2025/1/12 22:52:21

引言

前面文章OpenHarmony 入门——初识JS/ArkTS 侧的“JNI” NAPI(一) 和 OpenHarmony 入门——初识JS/ArkTS 侧的“JNI” NAPI 常见的函数详解(二)介绍了NAPI的基础理论知识,今天重点介绍下如何去开发一个自己的NAPI 接口。

一、引入 #include "napi/native_api.h"实现C/C++ 的函数体

首先是在C/C++文件中引入 #include “napi/native_api.h”,然后就是按照普通逻辑实现C/C++ 的函数体。

#include "napi/native_api.h"

二、定义要开放的NAPI 接口函数与Native 函数的映射关系

即通过napi_define_properties、DECLARE_NAPI_PROPERTY、DECLARE_NAPI_STATIC_PROPERTY 将C/C++ 中的Native 函数或者属性开放给JavaScript 使用,通常是在框架自带的函数static napi_value Init(napi_env env, napi_value exports) 中完成映射的

EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports) {
    napi_property_descriptor desc[] = {
        {"Init", nullptr, ObjectDectionInit, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"Process", nullptr, ObjectDectionProcess, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"DeInit", nullptr, ObjectDectionDeInit, nullptr, nullptr, nullptr, napi_default, nullptr}};
    napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
    return exports;
}
EXTERN_C_END

也可以换成宏的形式

EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports) {
    napi_property_descriptor desc[] = {
        DECLARE_NAPI_FUNCTION("Init", ObjectDectionInit),
        DECLARE_NAPI_FUNCTION("Process", ObjectDectionProcess),
        DECLARE_NAPI_FUNCTION("DeInit", ObjectDectionDeInit)
         };
    napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
    return exports;
}
EXTERN_C_END

这样子就建立完成了相关的映射关系,其中Init为ETS 侧调用的接口,而ObjectDectionInit则为Native 侧函数的真正实现,其他的类似。

三、建立接口与Module 的映射关系

1、定义一个napi_module 对象

主要就是定义一个napi_module 对象,把对应的so 绑定到nm_modname属性中,并且绑定了注册Module的入口函数。

/*
 * Napi Module define
 */
static napi_module msLiteModule = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = Init,
    .nm_modname = "mslite_napi",
    .nm_priv = ((void *)0),
    .reserved = {0},
};

这样子绑定后就可以在ETS侧通过 import mslite_napi from ‘libmslite_napi.so’; 后

import mslite_napi from 'libmslite_napi.so';

let resourceManager = context.resourceManager;
mslite_napi.Init(resourceManager );
mslite_napi.Process(this.modelId, picDesc, buffer).then((value: InferResult) => {
                callback(value.objects);
              }).catch((err: BusinessError) => {
               
              });
mslite_napi.DeInit();

2、napi_module 对象绑定到系统函数RegisterModule中

然后再把napi_module 对象 传入到系统函数RegisterModule中

/*
 * module register
 */
extern "C" __attribute__((constructor)) void RegisterModule(void) {
  MS_LOG(INFO) << "RegisterModule() is called";
  napi_module_register(&g_module);
}

3、执行napi_module 的入口函数nm_register_func

一般来说在OH系统中内置的NAPI 的注册入口函数是由Framework自动去执行,所以呢我们自己开发NAPI的话需要自己去调用触发才行。

四、定义ETS 接口描述文件.d.ts

在这里插入图片描述
假如dts文件里定义了两个同名接口,也只需要定义映射一个即可,因为对js或ets来说sendFile对外接口名相同,在C++实现时根据传入的参数个数或者参数类型来判断js或ets调用哪个函数

export const Init: (path:Object) => number;
export const Process: (modeid:number, picDesc:Object, buffer: ArrayBuffer) => number;
export const DeInit: () => number;

五、在Native侧实现接收和处理ETS的传参及反馈结果

因为在ETS侧你可以认为数据类型是弱类型的,具体的类型是什么,需要由Native侧自己去解析,对了Native侧接收ETS侧传递过来的函数的形参列表及返回值都是固定类型的。再以为例@ohos.myapp.d.ts

declare namespace myapp {
    // 同步方法
    function getVisitCountSync(key: string, defaultValue?: string): string;
    // 异步方法
    function getVisitCountAsync(key: string, callback: AsyncCallback<string>): void; // callback回调方式
    function getVisitCountAsync(key: string, defaultValue?: string): Promise<string>; // Promise方式
 }
#define GET_PARAMS(env, info, num)    \
    size_t argc = num;                \
    napi_value argv[num] = {nullptr}; \
    napi_value thisVar = nullptr;     \
    void *data = nullptr;             \
    napi_get_cb_info(env, info, &argc, argv, &thisVar, &data)

static napi_value ObjectDectionInit(napi_env env, napi_callback_info info) {
    int32_t ret;
    int32_t modelId;
    napi_value error_ret;
    napi_create_int32(env, -1, &error_ret);
    napi_value success_ret;

    napi_value result = nullptr;
    napi_get_undefined(env, &result);

    GET_PARAMS(env, info, 1);
    const std::string modelName = "yolov5s_pruned.ms"; 
    size_t modelSize;
    auto resourcesManager = OH_ResourceManager_InitNativeResourceManager(env, argv[0]);
    auto modelBuffer = ReadModelFile(resourcesManager, modelName, &modelSize);
    modelms = CreateMSLiteModel(modelBuffer, modelSize);

    napi_create_int32(env, modelId, &success_ret);
    return success_ret;
}

static napi_value ObjectDectionProcess(napi_env env, napi_callback_info info) {
    napi_value promise = nullptr;
    size_t argc = ARGS_THREE;
    napi_value argv[ARGS_THREE] = {nullptr};
    napi_value thisArg;
    int32_t ret = napi_get_cb_info(env, info, &argc, argv, &thisArg, nullptr);
    int32_t modelId = 0;
    PicDescNapi picDescNapi{};
    std::string data = "";
    napi_value undefinedResult = nullptr;
    napi_get_undefined(env, &undefinedResult);
    std::unique_ptr<MsAIAsyncContext> asyncContext = std::make_unique<MsAIAsyncContext>();

    for (size_t i = PARAM0; i < argc; i++) {
        if (i == PARAM0) {
            napi_status status = napi_get_value_int32(env, argv[i], &modelId);
            if ((status != napi_ok) || (modelId < 0)) {
                return undefinedResult;
            }
        } else if (i == PARAM1) {
            ret = ParsePicDesc(env, argv[i], picDescNapi);
            if (ret != RETCODE_SUCCESS) {
                return undefinedResult;
            }
        } else if (i == PARAM2) {
            char *array_buffer_data;
            size_t array_buffer_total;
            napi_status status = napi_get_arraybuffer_info(env, argv[i], reinterpret_cast<void **>(&array_buffer_data),
                                                           &array_buffer_total);
            if ((status != napi_ok) || (array_buffer_total <= 0)) {
                return undefinedResult;
            }
            data.assign(array_buffer_data, array_buffer_data + array_buffer_total);
        } else {
            LOGE("Invalid input params.");
            return undefinedResult;
        }
    }
    SetData(asyncContext, modelId, picDescNapi, data);
    napi_value resourceName = nullptr;
    napi_create_string_utf8(env, "Process", NAPI_AUTO_LENGTH, &resourceName);
    auto status = napi_create_promise(env, &asyncContext->deferred, &promise);
    if (status != napi_ok) {
        return undefinedResult;
    }
    if (1) {
        status = napi_create_async_work(env, nullptr, resourceName, ExecuteCB, PromiseCompleteCB,
                                        static_cast<void *>(asyncContext.get()), &asyncContext->asyncWork);
        async_creat = 1;
    }
    status = napi_queue_async_work(env, asyncContext->asyncWork);
    if (status == napi_ok) {
        asyncContext.release();
    } else {
        return undefinedResult;
    }
    return promise;
}

static napi_value ObjectDectionDeInit(napi_env env, napi_callback_info info) {
    napi_value success_ret;
    napi_create_int32(env, 0, &success_ret);
    return success_ret;
}

1、开发同步接口

C开发者做好数据的转换工作就可以了。JavaScript调用传递的参数对象、函数对象都是以napi_value这样一个抽象的类型提供给C的,开发者需要将它们转换为C数据类型进行计算,再将计算结果转为napi_value类型返回就可以了。NAPI框架提供了各种api接口为用户完成这些转换,这些转换工作背后是依赖JS引擎去实现的。

static napi_value GetVisitCountSync(napi_env env, napi_callback_info info) {    
  /* 根据环境变量获取参数 */
  size_t argc = 2; //参数个数
  napi_value argv[2] = { 0 }; //参数定义
 
  /* 入参变量获取 */
  napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr);
  // 获取入参的类型
  napi_valuetype valueType = napi_undefined;
  napi_typeof(env, argv[0], &valueType);    
 
  // 入参值转换为C/C++可以操作的数据类型
  char value[VALUE_BUFFER_SIZE] = { 0 };
  size_t valueLen = 0;
  napi_get_value_string_utf8(env, argv[0], value, VALUE_BUFFER_SIZE, &valueLen);
 
  // ...... 省略若干业务流程计算步骤
 
  /* C/C++数据类型转换为JS数据类型并返回 */
  napi_value result = nullptr; // JS字符串对象
  std::string resultStr = "Visit Count = 65535";
  napi_create_string_utf8(env, resultStr.c_str(), resultStr.length(), &result);
 
  return result; //返回JS对象
}

2.1、函数声明

每一个映射后的函数,必须是参数napi_env env, napi_callback_info cbinfo,返回值为napi_value。为了实现js或ets调用,NAPI框架需要解决以下问题,数据传递与转换,js/ets传入的入参、得到的返回结果,需要转换成C/C++代码可以操作的数据类型,因此NAPI框架引入了一个中间的数据类型,来分别对应上层js/ets与C/C++的类型,以及数据类型的操作方法。

2.2、获取入参

napi_get_cb_info从cbinfo参数中获取JavaScript传入参数,

2.3、把NAPI类型转换为C/C++可识别的类型

napi_value NapiDemo(napi_env env, napi_callback_info cbinfo)
{
	...
    char* type = nullptr;
    size_t typeLen = 0;
    napi_get_value_string_utf8(env, argv[0], nullptr, 0, &typeLen);
    NAPI_ASSERT(env, typeLen > 0, "typeLen == 0");
    type = new char[typeLen + 1];
    napi_get_value_string_utf8(env, argv[0], type, typeLen + 1, &typeLen);
	...
}

2.4、返回值

当C++没有返回值时NapiDemo将nullptr返回,NAPI框架没有nullptr,通过napi_get_undefined将nullptr转换成

napi_undefined。
napi_value NapiDemo(napi_env env, napi_callback_info cbinfo)
{
...
    napi_value result = nullptr;
    napi_get_undefined(env, &result);
    return result;
}

2、开发异步接口

C++实现NAPI异步接口需要做到三步:

  • 同步返回结果给JS/ETS调用者
  • 另起线程完成异步操作
  • 通过回调(callback)或Promise将异步操作结果返回
//异步方法需要在不同线程中传递各种业务数据,定义一个结构体保存这些被传递的信息
struct MyAsyncContext {
    napi_env env = nullptr; // napi运行环境
    napi_async_work work = nullptr; // 异步工作对象
    napi_deferred deferred = nullptr; // 延迟执行对象(用于promise方式返回计算结果)
    napi_ref callbackRef = nullptr; // js callback function的引用对象 (用于callback方式返回计算结果)
};

static napi_value GetVisitCountAsync(napi_env env, napi_callback_info info)
{
    ...... // 省略部分前置代码
    // 首先还是读取JS入参
    napi_value argv[2] = { 0 };
    napi_get_cb_info(env, info, &argc, argv, &thisVar, &data);
    
    auto asyncContext = new MyAsyncContext(); //创建结构体用于保存各种需要在异步线程中传递的数据信息
    asyncContext->env = env;

    // callback回调方式和promise回调在ts接口文件中体现为2个独立的接口方法,但它们的接口名称相同,在C++侧是由同一个方法来实现的。
    // 这里通过判断JS传入的第二个参数是不是function类型,来判定用户调用的是callback回调接口,还是promise回调接口。
    napi_valuetype valueType = napi_undefined; 
    napi_typeof(env, argv[1], &valueType);

    // 为异步方法创建临时返回值。根据我们的ts接口文件定义,callback接口返回一个void就行, promise接口返回一个promise对象
    napi_value tmpRet = nullptr;
    if (valueType == napi_function) { // Js调用的是callback接口
        // 为js调用者传入的js fuction创建一个napi引用并保存到asyncContext中,以便后续在C++异步线程中能够回调该js fuction
        napi_create_reference(env, argv[1], 1, &asyncContext->callbackRef);    
        // callback接口返回参数为void,构造一个undefined的返回值即可。
        napi_get_undefined(env, &tmpRet);
    } else { // Js调用的是promise接口
        // 创建promise对象。tmpRet用于返回promise对象给js调用者, asyncContext->deferred用于后续在C++的异步线程中返回正真的计算结果
        napi_create_promise(env, &asyncContext->deferred, &tmpRet);
    }

    napi_value resource = nullptr;
    // 创建异步工作  (内部实际是使用了libuv组件的异步处理能力,需要开发者自定义两个callback方法)
    napi_create_async_work(
        env, nullptr, resource,
        [](napi_env env, void* data) { // 1)execute_callback 方法,该方法会在libuv新开的独立线程中被执行
            MyAsyncContext* innerAsyncContext = (MyAsyncContext*)data;
            // 需要异步处理的业务逻辑都放在这个execute_callback方法中,运算需要的数据可以通过data入参传进来。
            innerAsyncContext->status = 0;
            // ......
        },
        [](napi_env env, napi_status status, void* data) { // 2)complete_callback方法,在js应用的主线程中运行
            MyAsyncContext* innerAsyncContext = (MyAsyncContext*)data;
            napi_value asyncResult;
            // complete_callback回到了主线程,一般用于返回异步计算结果。execute_callback和complete_callback之间可以通过data传递数据信息
            // 计算结果一般是从data中获取的,这里略过直接硬编码
            napi_create_string_utf8(env, "Visit Count = 65535", NAPI_AUTO_LENGTH, &asyncResult);
            if (innerAsyncContext->deferred) {
                // promise 方式的回调
                // innerAsyncContext->deferred是前面步骤中创建的promise延迟执行对象(此时js调用者已经拿到了该promise对象)
                napi_resolve_deferred(env, innerAsyncContext->deferred, asyncResult);
            } else {
                // callback 函数方式的回调
                napi_value callback = nullptr;
                // 通过napi_ref获取之间js调用者传入的js function,并调用它返回计算结果
                napi_get_reference_value(env, innerAsyncContext->callbackRef, &callback);
                napi_call_function(env, nullptr, callback, 1, &asyncResult, nullptr);
                napi_delete_reference(env, innerAsyncContext->callbackRef);
            }
            // 在异步调用的结尾释放async_work和相关业务数据的内存
            napi_delete_async_work(env, innerAsyncContext->work);
            delete innerAsyncContext; 
        },
    (void*)asyncContext, &asyncContext->work);

  // 执行异步工作
  napi_queue_async_work(env, asyncContext->work);
  // 返回临时结果给js调用 (callback接口返回的是undefined, promise接口返回的是promise对象)
  return tmpRet;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1968662.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

maven插件2(spring-api-auth-valid-plugin)

https://maven.apache.org/guides/mini/guide-configuring-plugins.htmlhttps://maven.apache.org/plugin-testing/maven-plugin-testing-harness/getting-started/index.html plugin-desc 业务功能 所有的endpoint,必须带有指定的安全校验标签,如spring-security的PreAuthori…

RabbitMQ 集群安装

在 linux 下手动安装 RabbitMQ 集群。 准备 安装之前的准备工作。 准备内容说明其他3 台服务器centos、redhat 等ErlangRabbitMQ 运行需要的基础环境socatRabbitMQ 运行需要的基础环境logrotateRabbitMQ 运行需要的基础环境这个服务器一般自带了 下面的安装示例中使用的版本…

一键测量仪,能否彻底解决燃气灶配件缺陷问题?

燃气灶配件是指用于燃气灶的附件或零部件&#xff0c;用于安装、维护或改进燃气灶的功能和性能。这些配件通常包括各种零部件、附件和替换件&#xff0c;以确保燃气灶的正常运行和安全使用。燃气灶的火焰头是产生火焰的部件&#xff0c;通常根据不同的燃气类型和火力需求选择合…

python-求四位数(赛氪OJ)

[题目描述] 3025 这个数具有一种独特的性质&#xff1a;将它平分为二段&#xff0c;即 30 和 25&#xff0c;使之相加后求平方&#xff0c;即 (3025)^2&#xff0c;恰好等于 3025 本身。请求出具有这样性质的全部四位数。输入格式&#xff1a; 此题没有输入。输出格式&#xff…

详解并掌握AXI4总线协议(一)、AXI4-FULL接口介绍

系列文章目录 文章目录 系列文章目录一、AXI介绍二、AXI4、AXI-Lite、AXI4-Stream区别三、AXI4读写架构3.1 通道定义3.2 读突发时序3.3 写突发时序 四、AXI4-FULL 总线信号介绍4.1全局信号4.2 写地址通道信号4.3 写数据通道信号4.4 写响应通道信号4.5 读地址通道信号4.6 读数据…

Animate软件基础:在时间轴中添加或插入帧

FlashASer&#xff1a;AdobeAnimate2021软件零基础入门教程https://zhuanlan.zhihu.com/p/633230084 FlashASer&#xff1a;实用的各种Adobe Animate软件教程https://zhuanlan.zhihu.com/p/675680471 FlashASer&#xff1a;Animate教程及作品源文件https://zhuanlan.zhihu.co…

抖音爆火的“拆盲盒”直播,是如何将昂贵的废品卖给消费者的?

抖音直播间掀起了一股“拆盲盒”热潮。 最初&#xff0c;这股热潮主要集中在拆卡直播间。一盒10包起卖的卡牌&#xff0c;价格在100~200不等。拆卡主播拿起剪刀行云流水的开盒、拆卡、过牌&#xff0c;一晚上能轻松跑出数万元的营业额。数据显示&#xff0c;头部卡牌公司卡游仅…

Redis+Unity 数据库搭建

游戏中需要存放排行榜等数据&#xff0c;而且是实时存放&#xff0c;所以就涉及到数据库的问题。这里找服务器大神了解到可以用Redis来做存储&#xff0c;免费的效率极高。 Redis的搭建参考上文的文章&#xff0c;同时也感谢这位网友。 搭建Redis 并测试数据 搭建Redis 1.下…

玩转云服务:Google Cloud谷歌云永久免费云服务器「白嫖」 指南

前几天&#xff0c;和大家分享了&#xff1a; 玩转云服务&#xff1a;Oracle Cloud甲骨文永久免费云服务器注册及配置指南 相信很多同学都卡在了这一步&#xff1a; 可用性域 AD-1 中配置 VM.Standard.E2.1.Micro 的容量不足。请在其他可用性域中创建实例&#xff0c;或稍后…

Kafka设计与原理详解

RocketMQ 是一款开源的分布式消息系统&#xff0c;基于高可用分布式集群技术&#xff0c;提供低延时的、高可靠的消息发布与订阅服务。同时&#xff0c;广泛应用于多个领域&#xff0c;包括异步通信解耦、企业解决方案、金融支付、电信、电子商务、快递物流、广告营销、社交、即…

使用kettle开源工具进行跨库数据同步

数据库同步可以用&#xff1a; 1、Navicat 2、Kettle 3、自己写代码 调用码神工具跨库数据同步 -连接 4、其它 实现 这里使用Kettle来同步&#xff0c;主要是开源的&#xff0c;通过配置就可以实现了 Kettle的图形化界面&#xff08;Spoon&#xff09;安装参考方法 ht…

Maven实战.依赖(依赖范围、传递性依赖、依赖调解、可选依赖等)

文章目录 依赖的配置依赖范围传递性依赖传递性依赖和依赖范围依赖调解可选依赖最佳实践排除依赖归类依赖优化依赖 依赖的配置 依赖会有基本的groupId、artifactld 和 version等元素组成。其实一个依赖声明可以包含如下的一些元素&#xff1a; <project> ...<depende…

单例模式及其思想

本文包括以下几点↓ 结论&#xff1a;设计模式不是简单地将一个固定的代码框架套用到项目中&#xff0c;而是一种严谨的编程思想&#xff0c;旨在提供解决特定问题的经验和指导。 单例模式&#xff08;Singleton Pattern&#xff09; 意图 旨在确保类只有一个实例&#xff…

Linux用户-用户组

作者介绍&#xff1a;简历上没有一个精通的运维工程师。希望大家多多关注我&#xff0c;我尽量把自己会的都分享给大家&#xff0c;下面的思维导图也是预计更新的内容和当前进度(不定时更新)。 Linux是一个多用户多任务操作系统,这意味着它可以同时支持多个用户登录并使用系统。…

每日OJ_牛客HJ74 参数解析

目录 牛客HJ74 参数解析 解析代码1 解析代码2 牛客HJ74 参数解析 参数解析_牛客题霸_牛客网 解析代码1 本题通过以空格和双引号为间隔&#xff0c;统计参数个数。对于双引号&#xff0c;通过添加flag&#xff0c;保证双引号中的空格被输出。 #include <iostream> #i…

解决文件夹打不开难题:数据恢复全攻略

在日常的电脑使用过程中&#xff0c;遇到文件夹无法打开的情况无疑是令人头疼的。这不仅可能影响到我们的工作效率&#xff0c;还可能导致重要数据的丢失。本文将深入探讨文件夹打不开的原因&#xff0c;并为您提供两种高效的数据恢复方案&#xff0c;助您轻松应对这一难题。 一…

p33 指针详解(1)(2)(3)

指针的进阶 1.字符指针 void test(int arr[]) { int szsizeof(arr)/sizeof(arr[0]); printf("%d\n", sz); } int main() { int arr[10] {0}; test(arr); return 0; } 这个代码在64位计算机中是8/42 在32位计算机中的是4/41 int main() {char c…

vue2 搭配 html2canvas 截图并设置截图时样式(不影响页面) 以及 base64转file文件上传 或者下载截图 小记

下载 npm install html2canvas --save引入 import html2canvas from "html2canvas"; //使用 html2canvasForChars() { // 使用that来存储当前Vue组件的上下文&#xff0c;以便在回调函数中使用 let that this; // 获取DOM中id为"charts"的元素&…

3.1 拓扑排序

有向图的存储 邻接矩阵 邻接表 拓扑排序 有向无环图&#xff1a;不存在环的有向图 环&#xff1a; 在有向图中&#xff0c;从一个节点出发&#xff0c;最终回到它自身的路径被称为环 入度&#xff1a; 以节点x为终点的有向边的条数被称为x的入度 出度&#xff1a; 以节…