003_了解过鸿蒙的 Napi 吗?如果我需要编译三方库如何链接?
003_了解过鸿蒙的 Napi 吗?如果我需要编译三方库如何链接?
NAPI 框架简介
Node.js 的 N-API
- NAPI 其实是最早应该是来自 node.js 中的一个拓展库(也可以说是一整套 API 接口),叫 Node-API ,叫做 N-API 。是用来构建本地插件的 API,将所有的 nodejs 底层数据结构黑盒化,封装成二进制接口,这样就可以实现不同版本的 Node.js 使用同样的接口,其目的是为了简化开发和维护。
NAPI (OpenHarmony)
- NAPI,全称 Native API ,是 OpenHarmony 系统中的一套原生模块拓展开发框架,基于 Nodejs 中的 N-API 开发,为开发者提供了 JS 与 C/C++不同语言模块之间的相互访问,交互的能力。它可以用于规范化封装 IO、OS 底层等,并可以提供相应的 JS 接口供开发者调用。当然。 N-API 也可以做到这一点。
- 区别于 N-API ,主要在于 NAPI 针对 OpenHarmony 系统做了 一些适配化和优化。但二者的目的都是为了简化和统一原生模块的开发和维护,提高跨平台和跨版本的兼容性
JS 和 C/C++互相访问实现原理(浅谈)
鄙人浅谈一下这个东西,欢迎各位斧正!
- 不同的语言的数据类型采用的是 napi_value 类型做封装和转换(计算机网络协议既视感),而像函数等接口则采用如 napi_create_function() 以及 napi_call_function() 等来进行创建和调用。
- 使用到了 V8 引擎,且对 V8 的接口做了 黑盒化 和 抽象化,使得更加稳定。
Code Question
主要是记录一下在读以及编写 Code 时的遇到的问题的以及自己积累的心得体会。
大多是一些代码中的接口的解释和个人结合相关资料后的一点理解
#ifdef __cplusplus extern “C”
- 这是一个在 cpp 中的宏命令,其表示的是如果在 cpp 文件中,我们需要调用一个 C 文件的接口
- 背景:
a. C 和 C++ 对于函数名字处理的机制不同,众所周知,C++支持函数重载,因此在执行函数时会对名字有特殊处理,但是 C 不同,C 认为函数名只是一个名字。
b. 如果需要使用到 C 中写好的接口,需要使用 C 方式的链接,因此在需要 extern “C” 来提示编译器在将 cpp 文件转为汇编时将该处对接口的调用方式由 Cpp 方式改为 C 方式,从而可以正确链接。 - 好处:就是方便了开发,使得 Cpp 对 C 的兼容性更强,对于已经写得很好的 C 接口,无需用 Cpp 再写一份。
_attribute_((constructor))
- 这是 GCC 一个特有的语法,用来修饰一个函数,从而让该函数在“main”之前执行,所以可以用来做初始化以及其他准备工作,比如初始化块变量或注册回调函数。可以避免一些依赖问题,提高性能。
- 相反,
__attribute__((destructor))
可以修饰函数,使得这个函数在共享库卸载或者程序退出时执行。 - 这两个都是 C++ 11 标准中引入的属性指定符序列中的一种,属性指定符序列是一种标准语法。
- 该语法还可以携带一个优先级参数,用于指定多个构造函数的执行顺序,优先级越低,执行越早。
- 区别 static
a. static 变量是在全局变量初始化后,main 执行之前的,而__attribute__((constructor))
是在全局变量初始化之前执行,这样可以避免依赖问题。
b. static 只能在当前文件中使用,而__attribute__((constructor))
可以在不同文件或者动态链接库中使用。
NAPI_CALL
- 是一个接口函数,用来调用 JS 中的函数,参数包括环境变量,接收对象,函数对象,参数个数,参数数组,返回值
- 使用场景
a. 封装 IO、CPU 密集型、OS 底层能力,并将 JS 接口对外暴露。
b. 实现 JS 与 C/C++代码的互相访问。
c. 优先封装异步方法。 - 该函数与其他类型的接口函数的区别
a. 这是一个宏,可以用来检测 NAPI 函数的返回值是否正确,其他类型的函数需要手动检测。
b. 可以调用 JS 中任意函数,无论是全局还是对象的,其他接口只能调用特定类型以及特定范围的接口。
c. 可以在任何地方需要回调的时候调用,不需要额外的参数以及 DS。 - 优势
a. 简化 NAPI 函数的调用和错误处理,提高 Code 的可读性和可维护性。
b. 可以方便调用 JS 中的接口,实现 C/C++和 JS 代码的互相访问
c. 任意调用,无需额外的参数以及 DS - 局限性
a. 宏,不能作为函数指针传递给其他函数
b. 不能直接处理异步操作,需要结合其他接口
c. 存在兼容性和稳定性问题。
DELCARE_NAPI_FUCTION
- 这是 NAPI 的一个宏,看名字大家都知道这个是用来声明一个函数的,黄同学在很多使用 NAPI 的 Cpp 代码都能看到这个宏。
- 宏定义原型(参数),有两种形式
1 | // 不传回调 |
- 两种形式
a. 传递三个参数(模块名,函数名,回调函数),这种形式最常见,由开发者定义回调函数的逻辑和返回值。
b. 传递两个参数(模块名,函数名),这种形式其实是一种简化写法,会自动使用一个默认的回调函数,将 JS 传递的参数转换为 C 的数据类型,并将 C 函数的返回值转化为 napi_value 类型返回给 JS。
c. 在使用宏的时候,会根据传递参数的个数来执行,这种方式其实就是宏的条件编译,而不是函数重载。 - 实现原理
a. 生成一个 napi_value 类型的函数,调用到 napi_create_function 函数,创建一个 JS 对象,并将回调函数作为 JS 对象的内部数据。
b. 将生成的函数添加到一个全局数组中,用于存储所有的 NAPI 模块接口函数。
c. 框架初始化的时候,遍历这个数组,将每个接口导出到 JS 中,方便调用。
d. JS 调用接口时,NAPI 框架会调出对应的回调函数,并将 JS 的参数和返回值转化为 napi_value,实现 JS 和 C/C++间的交互。
实现原理的流程图,不包括框架初始化
napi_get_cb_info
- napi 的一个函数,用于获取回调函数的参数和其他信息下面是它的原形
1 | napi_status napi_get_cb_info(napi_env env, napi_callback_info cbinfo, |
- 参数解释:
a. env:环境变量
b. cbinfo:回调函数信息
c. argc:接收参数个数的指针
d. argv:存放参数值的数组
e. this_arg 是 JS 中的 this 对象,data 是接收数据指针的指针。
NAPI 函数定义限制
这是一条使用框架编写一些 C/C++代码作为 JS 的接口的时需要注意的事情。
黄同学在做一个板子的 sample 的时候发现,某个 smaple 的样例源码无法跑通,除了一些简单的语法错误,最主要的是函数定义时的参数类型。
- 在本身数据是没有 NAPI 类型的数据的,但是框架中很多接口的定义都是用 napi 类型,所以我们在定义的时候,传递参数可以用 void* ,即空类型传递,然后在函数体内再对应修改即可。否则调用时会不符合 NAPI 中接口的定义。
异步实现机制
以下内容只讨论计算机方面,不要和我听异步电机啥的,黄同学表示考完控制后,看到电机这个东西真的头很大。
什么是异步
- 异步操作不需要等待结果返回,而同步操作则需要等待。
- 优缺点取决于应用场景
a. 异步会提高效率和响应速度,但会增加复杂度和难度。
b. 同步比较简单直接,缺点是会造成阻塞和资源浪费。 - 异步和多线程:
a. 首先,这是两个东西,虽然黄同学在很多时候也会把这两个东西弄混,但是确实是有区别。
b. 区别在于,多线程编程是异步机制的常见实现方式之一,但并不是唯一,所以我们在看很多异步的操作认为是多线程其实是没什么问题的。
- 在 NAPI 中,有两种实现异步操作:Calllback和 Promise 。
Callback
- 就是一般的回调函数机制,相对来说比 Promise 这种代码逻辑比较复杂,类似常见算法中递归的过程,相对来说,代码的可读性比较差,你只需回想一下你第一次看递归代码的时候大概就知道这种过程了。
- 优点
a. 是 JS 的原生特性,无需额外的库或抽象层。
b. 可以在完成异步操作后执行一些操作。 - 缺点
a. 代码的可读性和可维护性差,当多个回调嵌套时,就会出现传说中的 回调地狱。
b. 对错误处理变得复杂,因为每个回调都需要检查错误并传递给下一个回调。
c. 不能返回多个参数,只能返回一个对象(有些资料对这个的解释是 Callback 是以参数的形式返回结果,但是并不准确,参考 JS 官方文档以及 javascript - How do you properly returnmultiple values from a Promise? - Stack Overflow 等内容,所谓的参数应该是一个参数,或者说是参数就是对象) - Code(JS)
1 | // 定义一个异步的除法函数,接受两个数字和两个回调函数作为参数 |
Promise
- Promise 其实就是一种封装的异步操作结果的对象。
- 三种状态
a. pending ,等待
b. fulfilled ,已成功
c. rejected ,已失败 - 优点:
a. 可以采用同步的方式编写异步代码,因为异步操作结果已经被封装成了 Promise,这样可以避免回调地狱。
b. Promise 对象本身是用链式而不是回调的方式调用,利用链式调用可以组合多个异步操作,并且可以用 then 或者 catch 操作来处理异步操作结果成功或者失败的情况。
c. 比较灵活,可以用 all 方法等待所有异步操作的完成,也可以用 race 方法来获取最先完成的异步操作的结果。 - 缺点
a. 因为做了一定程度的封装,用对象来存储异步操作的结果,其实就会消耗一些额外的内存和性能。
b. 违背异步非阻塞 I/O 的原则,因为需要等待异步操作的完成(当从 pending 变到 fulfilled 或者 rejected 时,这个过程是不可逆的,使用 await 关键字在 async 函数中,等待一个 Promise 对象,实际过程就是再那个时间段代码调用到异步操作,此时 async 不执行,有点类似于同步操作,或者说我们一开始学习编程时最简单的函数调用,详见 asynchronous - Why use promise or async/await on child processes in Node.js? - Stack Overflow 和 How to use promises - Learn web development | MDN (mozilla.org))。
c. 也是只能返回一个对象。 - Code( JS )
1 | // 定义一个异步的除法函数,返回一个Promise对象 |
NAPI 引入三方库
- 引言
- 在 Node.js 环境中,我们经常需要使用第三方库来扩展我们的应用程序的功能。而在使用 Node.js 的 C 插件开发时,我们可以使用 NAPI(Node.js API)来实现对 C 库的引入和使用。NAPI 提供了一套稳定的 API,使得我们可以方便地在 Node.js 中使用 C++库。本文将介绍如何使用 NAPI 引入三方库,并提供示例代码。
- NAPI 简介
- NAPI 是一组 C 语言的 API,用于构建 Node.js 的 C 插件。它提供了一套稳定的 API,使得我们可以直接在 C 代码中与 JavaScript 对象进行交互。使用 NAPI 可以提高插件的可移植性,并减少因 Node.js 版本变化而引起的代码修改。
- 引入三方库
- 为了在 Node.js 中引入三方库,我们需要先将该库的头文件和库文件进行编译。以 CMake 为例,我们可以使用以下 CMakeLists.txt 文件来编译一个名为”mylib”的三方库:
1 | cmake_minimum_required(VERSION 3.10) |
- 在 CMakeLists.txt 文件中,我们首先指定了项目的名称为”mylib”,然后指定了源文件为”mylib.cpp”,这是我们自己编写的库代码。接着,我们使用 add_library 命令来构建一个动态链接库,其中 SHARED 关键字表示构建的是一个共享库。最后,我们使用 target_include_directories 命令将当前源文件目录添加到库的头文件搜索路径中。
- 编译完成后,我们将得到一个名为”mylib.so”的共享库文件。
- NAPI 引入库
- 在 Node.js 插件的 C++代码中,我们可以使用 NAPI 提供的函数来导入和使用三方库。首先,我们需要包含”napi.h”头文件,并根据需要引入其他头文件。我们还需要使用 NAPI_MODULE_INIT 宏来初始化模块。
- 以下是一个使用 NAPI 引入”mylib”库的示例代码:
1 |
|
- 在以上代码中,我们首先包含了”Napi.h”和”mylib.h”头文件。然后,我们定义了一个名为 MyFunction 的函数,它接受一个 Napi::CallbackInfo 对象作为参数,该对象提供了关于调用函数的信息。在函数中,我们调用了 mylib 库中的函数,并将结果转换为 JavaScript 值。最后,我们使用 NAPI_MODULE 宏来初始化模块,将 MyFunction 函数导出为 JavaScript 函数。
- 使用 NAPI 插件
- 要在 Node.js 中使用我们创建的 NAPI 插件,我们首先需要将插件编译为共享库。然后,我们可以使用 require 函数来引入插件并调用其中的函数。
- 以下是一个使用我们创建的 NAPI 插件的 Node.js 代码示例:
1 | const addon = require('./build/Release/mylib'); |
- 在以上代码中,我们使用 require 函数引入了我们创建的插件。然后,我们调用了插件中导出的 myFunction 函数,并将结果打印到控制台。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 许小墨のBlog!
评论
WalineDisqus