OpenHarmony之NAPI框架介绍

张志成

诚迈科技高级技术专家

NAPI 是什么

NAPI 的概念源自 Nodejs,为了实现 javascript 脚本与 C++库之间的相互调用,Nodejs 对 V8 引擎的 api 做了一层封装,称为 NAPI。可以在 Nodejs 官网(nodejs.org/dist/latest...)上查看各种 NAPI 接口定义说明。

可以看到,NAPI 接口本身是 C++语言实现的,这些接口可以帮助 C++代码创建 JS 变量,或访问 JavaScript 运行环境中的 JS 变量与方法。

OpenHarmony 中的 NAPI

OpenAtom OpenHarmony(以下简称"OpenHarmony")应用层基于 javascript 语言开发,而系统框架层则基于 C++语言。它们之间需要一个桥梁来实现两种语言代码之间的相互调用,这个桥梁就是 NAPI。

这里可能有的小伙伴有疑问了:OpenHarmony 的 NAPI 和 NodeJs 的 NAPI 是一回事吗?应该说,OpenHarmony 系统沿用了 NAPI 的接口定义形式,但每个接口的内部实现都进行了重写。这是因为 NAPI 接口的本质是帮助 C++程序去跟 Javascript 引擎交互,因此对于不同的引擎需要有不同的实现方式。当用户调用了 NAPI 接口 napi_create_int64(), 对于 Nodejs 而言,它会去访问 V8 引擎的 api 创建一个 js 的数字变量,而对于 OpenHarmony,则是去访问 ArkUI 框架自己的 js 引擎(ArkNativeEngine)。在 OpenHarmony 源码中搜索 napi_create_int64() 方法,你会得到一份头文件定义:third_party\node\src\js_native_api.h 以及两份不同的实现代码:third_party\node\src\js_native_api_v8.ccfoundation\arkui\napi\native_engine\native_api.cppnative_api.cpp 是 OpenHarmony 版本的 NAPI 实现,想了解内部细节的可以从这里入手:

创建一个简单的 NAPI 工程

可以通过 DevEco Studio 的 Native C++模板创建一个包含简单 NAPI 实现的样例工程。

该工程自带一个 hello.cpp,实现了一个能够被 javascript 代码调用的 add()方法。

下面我们就基于这个简单的例子,探究一下 NAPI 框架的实现原理。

应用如何调用 NAPI 接口

应用代码导入对应的 so 库后,就可以调用该库实现的接口。

这里我们注意到,导入日志库时使用的名称是"@ohos.hilog",应用代码如果写成 import hilog from 'libhilog.z.so' 其实也是可以成功导入的。实际上,ArkUI 在运行时会将 @ohos.hilog 转换为 libhilog.z.so,然后到 /system/lib/module/ 目录下查找此库并加载。系统实现的 NAPI 库都放在/system/lib/module/目录下,类似的:@ohos.wifiManager 对应的是 /system/lib/module/libwifimanager.z.so;@ohos.deviceInfo 对应的是 /system/lib//module/libdeviceinfo.z.so

除了系统自带的 NAPI 库,应用也可以用 C++开发自己的 NAPI 库。上面例子中 import testNapi from 'libentry.so' 导入的就是应用自己实现的。应用开发的 NAPI 库会随着应用工程一起编译打包到 hap 文件中,最终部署到/data 目录每个应用自己的文件夹下。

NAPI 库的导入原理

我们知道,应用的 javascript 代码是由 ArkUI 的 JS 引擎解释执行的。当 JS 引擎解读 import hilog from '@ohos.hilog'; 这行代码时,会通过 dlopen() 将对应的 libhilog.z.so 加载到应用进程中。这一切是怎么做到的呢?每个应用进程在初始化时,都会创建一个引擎实例 ArkNativeEngineImpl,我们来看一下它的构造函数 foundation\arkui\napi\native_engine\impl\ark\ark_native_engine_impl.cpp

也就是说,每个应用进程的 JS 引擎中,都注册了一个"requireNapi"函数,当应用调用此方法时,JS 引擎就会通过 NAPI 框架的 moduleManager 类去处理 so 库的加载。moduleManager 内部最终是找到了/system/lib/module 下对应的 so 文件,并通过 dlopen()的方式加载到应用进程中。想了解细节的小伙伴可以读一下 NativeModuleManager::LoadNativeModule()方法的内部实现。

这里可能会有个疑问:应用的 javascript 代码中并没有写什么"requireNapi"的代码,只有 import xxx,怎么触发的导入处理函数?答案要到编译后的 js 代码中寻找。我们解开编译后的 hap 包,找到 ets 文件对应的 js 文件:

可以看到,index.ets 被编译成 index.js 后,import 关键字也被转为了"requireNapi",这样 JS 引擎在执行这行代码时,就会去调用注册的导入处理函数了。

C++库如何实现 JS 方法

前面解决了 JS 导 C++库的问题,下一步就是 JS 如何调用 C++库里的方法了。先说结论:一个 C++方法能否被应用调用,取决与 C++代码有没有将这个方法注册到 JS 引擎。

我们来看看 hello.cpp 是如何注册 add 方法的:

我们可以从下往上看这段代码:首先是 RegisterEntryModule(void) 方法。这是 C++向 JS 引擎进行 NAPI 模块与方法注册的起始代码。注意这个方法前面有个编译修饰符 "attribute((constructor))",它的作用是指导 C++代码的编译,使得当 so 库被加载到应用进程中时,RegisterEntryModule(void) 方法就会被自动调用到。该方法通过 NAPI 接口 napi_module_register() 向 JS 引擎注册了一个 napi_module。

然后是 Init()方法。该方法实现了 Add 方法的注册。也就是告诉 JS 引擎,将 JS 符号"add" 与 C++方法"Add" 进行关联映射。这样后续当 JS 引擎解释执行 javascript 代码 "testNapi.add(2, 3)"时,就会找到 C++ Add()方法的函数地址并调用。如下图所示:

方法关联调用的问题也解决了,最后就是 JS 运行环境与 C++运行环境的相互切换了。当 C++的 Add 方法被 JS 引擎调用到后,引擎会将 javascript 下发的参数变量传递给 C++。所有从 JS 运行环境传递过来的变量都是用 napi_value 类型来表示的。需要通过 NAPI 接口转为 C++语言的变量类型。详见下图每行代码的注释:

napi_value 不是一个具体的类型,它类似于 void*,表示的是 JS 变量在 JS 引擎内部存储区内的地址。需要通过对应的 NAPI 方法实现,例如:napi_get_value_int32() --- js 变量转为 c++整形 napi_get_value_string_utf8() --- js 变量转为 c++字符串 napi_get_value_bool() --- js 变量转为 c++布尔值

这些接口的具体用法和使用场景,可以参考 NodeJs 官方文档(nodejs.org/dist/latest...

C++程序链接 NAPI 库

OpenHarmony 的 NAPI 接口实现都封装在 libace_napi.z.so 中,C++程序编译时需链接此库。对于 DevEco Studio 应用开发的 cpp 代码,在对应的 CMakeLists.txt 中链接。该库文件在 SDK 目录下可以找到。

对于设备侧开发,系统框架中的 C++程序,则通过 BUILD.gn 文件定义依赖关系。

总结

NAPI 是 JavaScript 与 C++交互的桥梁。在 OpenHarmony 中,Javascript 代码在运行时由 ArkUI 的 JS 引擎解释执行,C++代码则通过 NAPI 接口访问 JS 引擎中的 Javascript 上下文,从而实现与 JS 变量、方法之间的相互调用。

参考链接

以下是源码仓库地址

gitee.com/openharmony...

gitee.com/openharmony...

相关推荐
一个处女座的程序猿1 小时前
AI之Agent之VibeCoding:《Vibe Coding Kills Open Source》翻译与解读
人工智能·开源·vibecoding·氛围编程
一只大侠的侠2 小时前
React Native开源鸿蒙跨平台训练营 Day16自定义 useForm 高性能验证
flutter·开源·harmonyos
IvorySQL3 小时前
PostgreSQL 分区表的 ALTER TABLE 语句执行机制解析
数据库·postgresql·开源
一只大侠的侠3 小时前
Flutter开源鸿蒙跨平台训练营 Day11从零开发商品详情页面
flutter·开源·harmonyos
一只大侠的侠3 小时前
React Native开源鸿蒙跨平台训练营 Day18自定义useForm表单管理实战实现
flutter·开源·harmonyos
一只大侠的侠4 小时前
React Native开源鸿蒙跨平台训练营 Day20自定义 useValidator 实现高性能表单验证
flutter·开源·harmonyos
晚霞的不甘4 小时前
Flutter for OpenHarmony 可视化教学:A* 寻路算法的交互式演示
人工智能·算法·flutter·架构·开源·音视频
晚霞的不甘5 小时前
Flutter for OpenHarmony 实现计算几何:Graham Scan 凸包算法的可视化演示
人工智能·算法·flutter·架构·开源·音视频
猫头虎6 小时前
OpenClaw-VSCode:在 VS Code 里玩转 OpenClaw,远程管理+SSH 双剑合璧
ide·vscode·开源·ssh·github·aigc·ai编程
一只大侠的侠6 小时前
Flutter开源鸿蒙跨平台训练营 Day12从零开发通用型登录页面
flutter·开源·harmonyos