【FFH】开源鸿蒙南向嵌入学习笔记——NAPI框架学习(一) 原创

FFH物联黄同学
发布于 2023-3-7 17:29
浏览
2收藏

开源鸿蒙南向嵌入学习笔记——NAPI框架学习(一)

前言——系列介绍

本系列文章主要是记录笔者在鸿蒙南向的学习与工作中的知识点笔记记录,其中不止会针对鸿蒙中的学习问题进行思考与记录,也会对涉及到的一些嵌入式等其他领域知识,自我学习的心得进行记录。

本篇内容主要是黄同学最近在OpenHarmony 南向开发学习中对NAPI框架以及一些代码中的接口,异步实现等机制的学习。

MindMap

【FFH】开源鸿蒙南向嵌入学习笔记——NAPI框架学习(一)-鸿蒙开发者社区

NAPI框架简介

Node.js 的 N-API

NAPI 其实是最早应该是来自node.js中的一个拓展库(也可以说是一整套API接口),叫Node-API,叫做N-API。是用来构建本地插件的API,将所有的nodejs底层数据结构黑盒化,封装成二进制接口,这样就可以实现不同版本的Node.js使用同样的接口,其目的是为了简化开发和维护。

NAPI (OpenHarmony)

  1. NAPI,全称 Native API,是OpenHarmony系统中的一套原生模块拓展开发框架,基于Nodejs中的N-API开发,为开发者提供了JS与C/C++不同语言模块之间的相互访问,交互的能力。它可以用于规范化封装IO、OS底层等,并可以提供相应的JS接口供开发者调用。当然。N-API也可以做到这一点。
  2. 区别于 N-API主要在于NAPI针对OpenHarmony 系统做了 一些适配化和优化。但二者的目的都是为了简化和统一原生模块的开发和维护,提高跨平台和跨版本的兼容性。

JS和C/C++互相访问实现原理(浅谈)

鄙人浅谈一下这个东西,欢迎各位斧正!

  1. 不同的语言的数据类型采用的是 napi_value类型做封装和转换(计算机网络协议既视感),而像函数等接口则采用如 napi_create_function() 以及 napi_call_function() 等来进行创建和调用。
  2. 使用到了 V8 引擎,且对 V8的接口做了 黑盒化抽象化,使得更加稳定。

Code Question

主要是记录一下在读以及编写Code时的遇到的问题的以及自己积累的心得体会。

大多是一些代码中的接口的解释和个人结合相关资料后的一点理解。

#ifdef __cplusplus extern “C”

  1. 这是一个在cpp中的宏命令,其表示的是如果在cpp文件中,我们需要调用一个C文件的接口
  2. 背景:
    1. C 和 C++ 对于函数名字处理的机制不同,众所周知,C++支持函数重载,因此在执行函数时会对名字有特殊处理,但是C不同,C认为函数名只是一个名字。
    2. 如果需要使用到C中写好的接口,需要使用C方式的链接,因此在需要 extern "C" 来提示编译器在将cpp文件转为汇编时将该处对接口的调用方式由Cpp方式改为C方式,从而可以正确链接。
  3. 好处:就是方便了开发,使得Cpp对C的兼容性更强,对于已经写得很好的C接口,无需用Cpp再写一份。

_attribute_((constructor))

  1. 这是GCC一个特有的语法,用来修饰一个函数,从而让该函数在“main”之前执行,所以可以用来做初始化以及其他准备工作,比如初始化块变量或注册回调函数。可以避免一些依赖问题,提高性能。
  2. 相反,__attribute__((destructor))可以修饰函数,使得这个函数在共享库卸载或者程序退出时执行。
  3. 这两个都是C++ 11 标准中引入的属性指定符序列中的一种,属性指定符序列是一种标准语法。
  4. 该语法还可以携带一个优先级参数,用于指定多个构造函数的执行顺序,优先级越低,执行越早。
  5. 区别static
    1. static 变量是在全局变量初始化后,main执行之前的,而__attribute__((constructor))是在全局变量初始化之前执行,这样可以避免依赖问题。
    2. static只能在当前文件中使用,而__attribute__((constructor))可以在不同文件或者动态链接库中使用。

NAPI_CALL

  1. 是一个接口函数,用来调用JS中的函数,参数包括环境变量,接收对象,函数对象,参数个数,参数数组,返回值
  2. 使用场景
    1. 封装IO、CPU密集型、OS底层能力,并将JS接口对外暴露。
    2. 实现JS与C/C++代码的互相访问。
    3. 优先封装异步方法。
  3. 该函数与其他类型的接口函数的区别
    1. 这是一个宏,可以用来检测NAPI函数的返回值是否正确,其他类型的函数需要手动检测。
    2. 可以调用JS中任意函数,无论是全局还是对象的,其他接口只能调用特定类型以及特定范围的接口。
    3. 可以在任何地方需要回调的时候调用,不需要额外的参数以及DS。
  4. 优势
    1. 简化NAPI函数的调用和错误处理,提高Code的可读性和可维护性。
    2. 可以方便调用JS中的接口,实现C/C++和JS代码的互相访问
    3. 任意调用,无需额外的参数以及DS
  5. 局限性
    1. 宏,不能作为函数指针传递给其他函数
    2. 不能直接处理异步操作,需要结合其他接口
    3. 存在兼容性和稳定性问题。

DELCARE_NAPI_FUCTION

  1. 这是NAPI的一个宏,看名字大家都知道这个是用来声明一个函数的,黄同学在很多使用NAPI的Cpp代码都能看到这个宏。

  2. 宏定义原型(参数),有两种形式

    // 不传回调
    #define DECLARE_NAPI_FUNCTION(modname,name)
    // 传递回调
    #define DECLARE_NAPI_FUNCTION_EX(modname, name, func) 
    
  3. 两种形式

    1. 传递三个参数(模块名,函数名,回调函数),这种形式最常见,由开发者定义回调函数的逻辑和返回值。
    2. 传递两个参数(模块名,函数名),这种形式其实是一种简化写法,会自动使用一个默认的回调函数,将JS传递的参数转换为C的数据类型,并将C函数的返回值转化为napi_value类型返回给JS。
    3. 在使用宏的时候,会根据传递参数的个数来执行,这种方式其实就是宏的条件编译,而不是 函数重载
  4. 实现原理

    1. 生成一个napi_value类型的函数,调用到napi_create_function函数,创建一个JS对象,并将回调函数作为JS对象的内部数据。
    2. 将生成的函数添加到一个全局数组中,用于存储所有的NAPI模块接口函数。
    3. 框架初始化的时候,遍历这个数组,将每个接口导出到JS中,方便调用。
    4. JS调用接口时,NAPI框架会调出对应的回调函数,并将JS的参数和返回值转化为napi_value,实现JS和C/C++间的交互。

实现原理的流程图,不包括框架初始化

<img src=“https://img-blog.csdnimg.cn/db6e57facd1b44a3b86d346065a18758.png” alt=“在这里插入图片描述” />

napi_get_cb_info

  1. napi的一个函数,用于获取回调函数的参数和其他信息下面是它的原形

    napi_status napi_get_cb_info(napi_env env, napi_callback_info cbinfo,
                                 size_t* argc, napi_value* argv,
                                 napi_value* this_arg, void** data);
    

    参数解释:

    1. env:环境变量
    2. cbinfo:回调函数信息
    3. argc:接收参数个数的指针
    4. argv:存放参数值的数组
    5. this_arg 是 JS中的this对象,data是接收数据指针的指针。

NAPI 函数定义限制

这是一条使用框架编写一些C/C++代码作为JS的接口的时需要注意的事情。

黄同学在做一个板子的sample的时候发现,某个smaple的样例源码无法跑通,除了一些简单的语法错误,最主要的是函数定义时的参数类型。

在本身数据是没有NAPI类型的数据的,但是框架中很多接口的定义都是用napi类型,所以我们在定义的时候,传递参数可以用void*,即空类型传递,然后在函数体内再对应修改即可。否则调用时会不符合NAPI中接口的定义。

异步实现机制

以下内容只讨论计算机方面,不要和我听异步电机啥的,黄同学表示考完控制后,看到电机这个东西真的头很大。

什么是异步

  1. 异步操作不需要等待结果返回,而同步操作则需要等待。
  2. 优缺点取决于应用场景
    1. 异步会提高效率和响应速度,但会增加复杂度和难度。
    2. 同步比较简单直接,缺点是会造成阻塞和资源浪费。
  3. 异步和多线程:
    1. 首先,这是两个东西,虽然黄同学在很多时候也会把这两个东西弄混,但是确实是有区别。
    2. 区别在于,多线程编程是异步机制的常见实现方式之一,但并不是唯一,所以我们在看很多异步的操作认为是多线程其实是没什么问题的。

NAPI中,有两种实现异步操作:**Calllback **和 Promise

Callback

  1. 就是一般的回调函数机制,相对来说比Promise这种代码逻辑比较复杂,类似常见算法中递归的过程,相对来说,代码的可读性比较差,你只需回想一下你第一次看递归代码的时候大概就知道这种过程了。
  2. 优点
    1. 是JS的原生特性,无需额外的库或抽象层。
    2. 可以在完成异步操作后执行一些操作。
  3. 缺点
    1. 代码的可读性和可维护性差,当多个回调嵌套时,就会出现传说中的 回调地狱
    2. 对错误处理变得复杂,因为每个回调都需要检查错误并传递给下一个回调。
    3. 不能返回多个参数,只能返回一个对象(有些资料对这个的解释是Callback是以参数的形式返回结果,但是并不准确,参考JS官方文档以及javascript - How do you properly return multiple values from a Promise? - Stack Overflow等内容,所谓的参数应该是一个参数,或者说是参数就是对象)
  4. Code(JS)
// 定义一个异步的除法函数,接受两个数字和两个回调函数作为参数
function divisionAPI(number, divider, successCallback, errorCallback) 
{
  if (divider == 0) 
  {
    return errorCallback(new Error("Division by zero"));
  }
  successCallback(number / divider);
}

// 调用异步的除法函数,传入两个数字和两个回调函数
divisionAPI(10, 2, function(result) {
  // 成功的回调函数,打印结果
  console.log("The result is " + result);
}, function(error) {
  // 失败的回调函数,打印错误
  console.error("Something went wrong: " + error.message);
});

Promise

  1. Promise 其实就是一种封装的异步操作结果的对象。
  2. 三种状态
    1. pending,等待
    2. fulfilled,已成功
    3. rejected,已失败
  3. 优点:
    1. 可以采用同步的方式编写异步代码,因为异步操作结果已经被封装成了Promise,这样可以避免回调地狱。
    2. Promise对象本身是用链式而不是回调的方式调用,利用链式调用可以组合多个异步操作,并且可以用then 或者 catch操作来处理异步操作结果成功或者失败的情况。
    3. 比较灵活,可以用all方法等待所有异步操作的完成,也可以用race方法来获取最先完成的异步操作的结果。
  4. 缺点
    1. 因为做了一定程度的封装,用对象来存储异步操作的结果,其实就会消耗一些额外的内存和性能。
    2. 违背异步非阻塞I/O的原则,因为需要等待异步操作的完成(当从pending变到fulfilled或者rejected时,这个过程是不可逆的,使用await关键字在async函数中,等待一个Promise对象,实际过程就是再那个时间段代码调用到异步操作,此时async不执行,有点类似于同步操作,或者说我们一开始学习编程时最简单的函数调用,详见asynchronous - Why use promise or async/await on child processes in Node.js? - Stack OverflowHow to use promises - Learn web development | MDN (mozilla.org))。
    3. 也是只能返回一个对象。
  5. Code(JS)
// 定义一个异步的除法函数,返回一个Promise对象
function divisionAPI(number, divider) {
  return new Promise(function(resolve, reject) {
    if (divider == 0) {
      return reject(new Error("Division by zero"));
    }
    resolve(number / divider);
  });
}

// 调用异步的除法函数,返回一个Promise对象
divisionAPI(10, 2)
  .then(function(result) {
    // 成功的回调函数,打印结果
    console.log("The result is " + result);
  })
  .catch(function(error) {
    // 失败的回调函数,打印错误
    console.error("Something went wrong: " + error.message);
  });

参考资料

黄同学写这篇blog的一些参考资料,有需要的可以看看。

  1. OpenHarmony 源码解析之NAPI框架内部实现分析-51CTO.COM
  2. [三方库移植之NAPI开发1]—Hello OpenHarmony NAPI - 知乎 (zhihu.com)
  3. ace_napi: Development framework for extending the JS Native Module | 原生模块扩展开发框架 (gitee.com)
  4. vendor_unionman: 该仓库托管广东九联科技股份有限公司厂商驱动及配置文件 - Gitee.com
  5. 写一个N-API没那么难? - 知乎 (zhihu.com)
  6. 为JS写C++扩展,Napi第一步_napi.h_番茄V王子的博客-CSDN博客
  7. c++ - How to save an asynchronous callback for later using node-addon-api / napi - Stack Overflow
  8. 三方库移植之NAPI开发–异步调用:Callback&Promise(四)-51CTO.COM
  9. 箭头函数表达式 - JavaScript |多核 (mozilla.org)
  10. What are the pros and cons of using promises instead of callbacks? - Best Interview Question
  11. asynchronous - Why use promise or async/await on child processes in Node.js? - Stack Overflow
  12. How to use promises - Learn web development | MDN (mozilla.org)

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
标签
3
收藏 2
回复
举报
1条回复
按时间正序
/
按时间倒序
yoshioimai
yoshioimai

很详细的学习笔记

回复
2023-3-30 17:18:49
回复
    相关推荐