HarmonyOS ArkUI实战开发-NAPI 加载原理(下)

上一节笔者给大家讲解了 JS 引擎解释执行到 import 语句的加载流程,总结起来就是利用 dlopen() 方法的加载特性向 NativeModuleManager 内部的链接尾部添加一个 NativeModule,没有阅读过上节文章的小伙伴,笔者强烈建议阅读一下,本节笔者继续给大家讲解 JS 调用 C++ 方法的实现过程。

回看requireNapi方法

根据上节课的讲解,napi_module_register() 方法只是通过 demoModule 的配置创建一个 NativeModule 后并把它加入到 NativeModuleManager 内部的链表尾部,当在 JS 侧调用 C++ 的对应方法时,如何能精准调用到对应方法的呢?我们再回头看下 ArkNativeEngine 构造方法中注册的 requireNapi() 方法内的执行过程,省略部分源码如下所示:

ArkNativeEngine::ArkNativeEngine(EcmaVM* vm, void* jsEngine, bool isLimitedWorker) : NativeEngine(jsEngine), vm_(vm), topScope_(vm), isLimitedWorker_(isLimitedWorker) {// 省略部分代码……void* requireData = static_cast<void*>(this);// 创建一个requireNapi()方法Local<FunctionRef> requireNapi =FunctionRef::New(vm,[](JsiRuntimeCallInfo *info) -> Local<JSValueRef> {NativeModuleManager* moduleManager = NativeModuleManager::GetInstance();NativeModule* module = nullptr;// 调用NativeModuleManager的LoadNativeModule方法加载module = moduleManager->LoadNativeModule();if (module != nullptr) {// 先判断 module 的 jsABCCode 或者 jsCode 是否为空则if (module->jsABCCode != nullptr || module->jsCode != nullptr) {// 省略部分代码……} else if (module->registerCallback != nullptr) {// 如果 module 的 registerCallback 不为空,则执行registerCallback() 方法module->registerCallback(reinterpret_cast<napi_env>(arkNativeEngine), JsValueFromLocalValue(exportObj));} else {HILOG_ERROR("init module failed");return scope.Escape(exports);}}return scope.Escape(exports);},nullptr,requireData);Local<ObjectRef> global = panda::JSNApi::GetGlobalObject(vm);Local<StringRef> requireName = StringRef::NewFromUtf8(vm, "requireNapi");// 注入 requireNapi 方法global->Set(vm, requireName, requireNapi);Init();panda::JSNApi::SetLoop(vm, loop_);
}

requireNapi() 方法内部先调用 NativeModuleManager 的 LoadNativeModule() 方法加载动态库并返回一个 module,如果 module 非空,则判断 module 中的 jsABCCode 或者 jsCode 是否为空,如果有一个非空则条件成立进入 if 语句,那么 jsABCCode 或者 jsCode 什么时候非空呢?比如加载的是项目中的一个模块而非一个单纯的动态库时条件才成立或者在跨平台的场景需要加载 abc 时条件成立,本文的样例只是加载了一个 libentry.so,因此条件不成立,接着判断 module 的 registerCallback 是否为空,registerCallback 是什么时机赋值的呢?笔者在上一节讲 napi_module_register() 中讲到过赋值,源码如下所示:

NAPI_EXTERN void napi_module_register(napi_module* mod)
{NativeModuleManager* moduleManager = NativeModuleManager::GetInstance();NativeModule module;module.version = mod->nm_version;module.fileName = mod->nm_filename;module.name = mod->nm_modname;// registerCallback 是 mod 中配置的nm_register_func方法module.registerCallback = (RegisterCallback)mod->nm_register_func;moduleManager->Register(&module);
}

在 napi_module_register() 方法内部把 mod 中配置的 nm_register_func 强制转换成 RegisterCallback 后赋值给了 NativeModule 的 registerCallback,这里可以进行强制转换利用的是 C++ 的一个特性:

在 C++ 中,函数指针类型的转换需要满足源类型和目标类型的函数签名(参数类型和数量,以及返回类型)完全相同。本样例中 nm_register_func 和 RegisterCallback 类型定义分别如下所示:

> typedef napi_value (*napi_addon_register_func)(napi_env env, napi_value exports);
> 
> typedef napi_value (*RegisterCallback)(napi_env, napi_value);

它们都接收两个参数:一个 napi_env 类型的 env 和一个 napi_value 类型的 exports,并返回一个 napi_value 类型的值,所以它们的函数签名是完全相同的,因此一个 napi_addon_register_func 类型的函数指针可以被强制转换为 RegisterCallback 类型的函数指针。

nm_register_func 就是在 hello.cpp 中配置的 Init() 方法,hello.cpp 的源码如下:

EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports) {// 创建一个napi_property_descriptor数组,napi_property_descriptor的每一项只配置了napi_property_descriptor desc[] = {{"add", nullptr, Add, nullptr, nullptr, nullptr, napi_default, nullptr},{"getMd5Sync", nullptr, GetMd5Sync, nullptr, nullptr, nullptr, napi_default, nullptr},{"getMd5", nullptr, GetMd5, nullptr, nullptr, nullptr, napi_default, nullptr},};// 调用napi_define_properties方法napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);return exports;
}
EXTERN_C_ENDstatic napi_module demoModule = {.nm_version = 1,.nm_flags = 0,.nm_filename = nullptr,.nm_register_func = Init, // nm_register_func被配置为 Init 方法.nm_modname = "entry",.nm_priv = ((void *)0),.reserved = {0},
};extern "C" __attribute__((constructor)) void RegisterEntryModule(void) {napi_module_register(&demoModule); 
}

综上所述,在 requireNapi() 方法内执行 module->registerCallback() 方法时就是执行的 hello.cpp 中配置的 Init() 方法,在 Init() 方法中先创建一个 napi_property_descriptor 类型的数组 desc,每一个 napi_property_descriptor 数据只配置了 utf8namemethod 和 attributes 这 3 项,然后调用 napi_define_properties() 方法,napi_define_properties() 方法源码如下所示:

NAPI_EXTERN napi_status napi_define_properties(napi_env env,napi_value object,size_t property_count,const napi_property_descriptor* properties)
{// 省略部分代码……for (size_t i = 0; i < property_count; i++) {NapiPropertyDescriptor property;// 有值property.utf8name = properties[i].utf8name;// 无值property.name = properties[i].name;// 有值property.method = reinterpret_cast<NapiNativeCallback>(properties[i].method);// 无值property.getter = reinterpret_cast<NapiNativeCallback>(properties[i].getter);// 无值property.setter = reinterpret_cast<NapiNativeCallback>(properties[i].setter);// 无值property.value = properties[i].value;// 有值且值为0property.attributes = (uint32_t)properties[i].attributes;// 无值property.data = properties[i].data;// 调用NapiDefineProperty方法NapiDefineProperty(env, nativeObject, property);}return napi_clear_last_error(env);
}

为了便于后续分析源码,笔者加上了详细的注释,napi_define_properties() 方法内部循环遍历传递进来的每一个 napi_property_descriptor,把每一个 napi_property_descriptor 转化成 NapiPropertyDescriptor 的 property 并调用 NapiDefineProperty() 方法完成 JS 方法和 C++方法的映射,NapiDefineProperty() 方法源码如下所示:

bool NapiDefineProperty(napi_env env, Local<panda::ObjectRef> &obj, NapiPropertyDescriptor propertyDescriptor)
{auto engine = reinterpret_cast<NativeEngine*>(env);auto vm = engine->GetEcmaVm();bool result = false;// 根据utf8name的名字创建一个JS引擎侧的字符串值赋值给propertyNameLocal<panda::StringRef> propertyName = panda::StringRef::NewFromUtf8(vm, propertyDescriptor.utf8name);// 校验attributes是否有设置其它值,本样例中attributes默认设置的是0,因此writable,enumable和configable都是false// writable: 属性是否可读可修改,enumable:属性是否允许遍历,configable:属性是否允许删除bool writable = (propertyDescriptor.attributes & NATIVE_WRITABLE) != 0;bool enumable = (propertyDescriptor.attributes & NATIVE_ENUMERABLE) != 0;bool configable = (propertyDescriptor.attributes & NATIVE_CONFIGURABLE) != 0;std::string fullName("");// 本样例中getter和setter都是为nullif (propertyDescriptor.getter != nullptr || propertyDescriptor.setter != nullptr) {// 省略部分代码……} else if (propertyDescriptor.method != nullptr) { // 本样例中method非空,配置的是C++端对应的方法名fullName += propertyDescriptor.utf8name;// 调用 NapiNativeCreateFunction方法创建一个 JS 引擎侧的方法cbObjLocal<panda::JSValueRef> cbObj = NapiNativeCreateFunction(env, fullName.c_str(), propertyDescriptor.method, propertyDescriptor.data);// 创建一个PropertyAttribute类型的attr实例PropertyAttribute attr(cbObj, writable, enumable, configable);// 调用JS引擎侧的JSObject对象的DefineProperty()方法完成对vm添加额外的属性操作result = obj->DefineProperty(vm, propertyName, attr);} else {Local<panda::JSValueRef> val = LocalValueFromJsValue(propertyDescriptor.value);PropertyAttribute attr(val, writable, enumable, configable);result = obj->DefineProperty(vm, propertyName, attr);}Local<panda::ObjectRef> excep = panda::JSNApi::GetUncaughtException(vm);if (!excep.IsNull()) {HILOG_ERROR("ArkNativeObject::DefineProperty occur Exception");panda::JSNApi::GetAndClearUncaughtException(vm);}return result;
}

NapiDefineProperty() 方法的内注释的比较清楚,主要是先根据 utf8name 创建一个 JS 引擎侧的方法名 propertyName,然后判断 getter 和 setter是否为空,本样例中它们都是空,接着判断 method 是否是空, 因为method 是我们在 hello.cpp 中定义的本地方法,所以条件成立进入当前分支语句中,fullName 表示 JS 侧的方法名,接着调用 NapiNativeCreateFunction() 方法创建一个 JS 引擎侧实例 cbObj,然后创建一个 PropertyAttribute 类型的 attr 实例,最后调用 JS 引擎侧的 JSObject 对象的 DefineProperty() 方法完成对 vm 添加额外的属性操作,也就是说代码分析到这里, JS 引擎内部已经保存了 JS 侧的方法名 和 C++ 侧的方法的映射关系。

好了,到目前为止,JS 侧的方法和 C++ 方法的关联我们已经清楚了,接下来看如何调用到 C++ 的方法……

JS调用C++方法

目前已经清楚了 JS 引擎已经保存了 JS 侧的方法名 和 C++ 侧的方法的映射关系,当 JS 侧需要调用 C++ 方法时,代码如下所示:

import testNapi from 'libentry.so'Text(this.message).fontSize(25).fontWeight(FontWeight.Bold).backgroundColor(Color.Pink).onClick(() => {var result = testNapi.add(2, 3);this.message = "OpenHarmony, value: " + result;console.log(this.message);})

笔者给 Text 添加了一个点击事件,当点击 Text 组件时执行了 testNapi.add(2, 3) 语句,JS 引擎解释执行到 testNapi.add() 方法时,就去查引擎内部维护的映射表,根据映射表可以找到 C++ 中定义的 Add() 方法,后续就是执行 C++ 中 Add() 方法的流程了……

码牛课堂也为了积极培养鸿蒙生态人才,让大家都能学习到鸿蒙开发最新的技术,针对一些在职人员、0基础小白、应届生/计算机专业、鸿蒙爱好者等人群,整理了一套纯血版鸿蒙(HarmonyOS Next)全栈开发技术的学习路线。大家可以进行参考学习:https://qr21.cn/FV7h05

①全方位,更合理的学习路径
路线图包括ArkTS基础语法、鸿蒙应用APP开发、鸿蒙能力集APP开发、次开发多端部署开发、物联网开发等九大模块,六大实战项目贯穿始终,由浅入深,层层递进,深入理解鸿蒙开发原理!

②多层次,更多的鸿蒙原生应用
路线图将包含完全基于鸿蒙内核开发的应用,比如一次开发多端部署、自由流转、元服务、端云一体化等,多方位的学习内容让学生能够高效掌握鸿蒙开发,少走弯路,真正理解并应用鸿蒙的核心技术和理念。

③实战化,更贴合企业需求的技术点
学习路线图中的每一个技术点都能够紧贴企业需求,经过多次真实实践,每一个知识点、每一个项目,都是码牛课堂鸿蒙研发团队精心打磨和深度解析的成果,注重对学生的细致教学,每一步都确保学生能够真正理解和掌握。

为了能让大家更好的学习鸿蒙(HarmonyOS NEXT)开发技术,这边特意整理了《鸿蒙开发学习手册》(共计890页),希望对大家有所帮助:https://qr21.cn/FV7h05

《鸿蒙开发学习手册》:https://qr21.cn/FV7h05

如何快速入门:

  1. 基本概念
  2. 构建第一个ArkTS应用
  3. ……

开发基础知识:https://qr21.cn/FV7h05

  1. 应用基础知识
  2. 配置文件
  3. 应用数据管理
  4. 应用安全管理
  5. 应用隐私保护
  6. 三方应用调用管控机制
  7. 资源分类与访问
  8. 学习ArkTS语言
  9. ……

基于ArkTS 开发:https://qr21.cn/FV7h05

  1. Ability开发
  2. UI开发
  3. 公共事件与通知
  4. 窗口管理
  5. 媒体
  6. 安全
  7. 网络与链接
  8. 电话服务
  9. 数据管理
  10. 后台任务(Background Task)管理
  11. 设备管理
  12. 设备使用信息统计
  13. DFX
  14. 国际化开发
  15. 折叠屏系列
  16. ……

鸿蒙开发面试真题(含参考答案):https://qr21.cn/FV7h05

大厂鸿蒙面试题::https://qr18.cn/F781PH

鸿蒙开发面试大盘集篇(共计319页):https://qr18.cn/F781PH

1.项目开发必备面试题
2.性能优化方向
3.架构方向
4.鸿蒙开发系统底层方向
5.鸿蒙音视频开发方向
6.鸿蒙车载开发方向
7.鸿蒙南向开发方向

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

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

相关文章

初识《list》及手搓模拟《list》

目录 前言&#xff1a; 1. list的介绍及使用 list的介绍&#xff1a; list的使用&#xff1a; 1、list的构造​编辑 2、list iterator的使用 3、list capacity 4、list element access 5、list modifiers 2.list的模拟实现 1、关于迭代器&#xff1a; 2、迭代器类的…

ScriptableObject数据容器讲解

概述 是Unity提供的一个用于创建可重用的数据容器或逻辑的基类。 ScriptableObject 是继承自 UnityEngine.Object 的一个类&#xff0c;但与普通的 MonoBehaviour 不同&#xff0c;它不能附加到GameObject上作为组件。 相反&#xff0c;ScriptableObject 通常用于存储和管理…

ThingsBoard实战教程(七):模拟设备遥测

tb做为一个多租户的物联网平台,文档也很齐全。后端使用的是java语言编写的,可以用swagger来导出文档。tb的所有服务都可以通过接口来看到。接口主要分为两部分,一部分是设备API,另一部分是用于服务端API。 在我们启动tb之后,可以通过http://ip +port/swagger-ui.html 来…

linux,从零安装nginx,并且部署vue应用程序

前言&#xff1a;系统使用龙蜥&#xff08;8.5&#xff09;的最小化安装&#xff0c;服务器安装这里不在赘述。 nginx 的版本&#xff1a;1.22.0 软件已经放在系统/home/software/ 一、安装nginx 进入路径/home/software/ 1》执行命令&#xff1a;rpm -ivh nginx-1.22.0-1.el7…

uboot大致流程总结

文章目录 一、uboot介绍二、uboot的配置编译过程2.1 make xxx_defconfig2.2 make 一、uboot介绍 uboot是一个bootloader&#xff0c;用于在嵌入式设备中引导linux内核启动&#xff0c;在嵌入式设备中常见的组织结构如下&#xff1a; 芯片内部固化代码 -> bootloader -> …

Docker NetWork (网络)

Docker 为什么需要网络管理 容器的网络默认与宿主机及其他容器都是相互隔离的&#xff0c;但同时我们也要考虑下面的一些问题&#xff0c; 比如 多个容器之间是如何通信的容器和宿主机是如何通信的容器和外界主机是如何通信的容器中要运行一些网络应用(如 nginx、web 应用、数…

第66天:API攻防-接口安全阿里云KEYPostmanDVWSXXE鉴权泄漏

案例一&#xff1a;安全问题-Dvws泄漏&鉴权&XXE 靶场地址&#xff1a;https://github.com/snoopysecurity/dvws-node 利用docker命令去启动 首先先注册一个账户 注册后登录点击admin area 发现点不进去 这里把bp打开但是不抓包&#xff0c;只做流量转发&#xff0c;进…

LLM学习笔记-3

温度缩放 概念 1&#xff09;在机器学习中&#xff0c;"温度缩放"通常指的是一种技术&#xff0c;用于调整神经网络输出的概率分布。这个技术通常在 softmax 函数的输出上进行操作。 2&#xff09;在 softmax 函数中&#xff0c;神经网络会输出一个概率分布&#x…

探索 虚拟化技术+Docker部署与操作

目录 一、你知道哪些云 1.1国内云 1.2国外云 二、Iaas、 Paas、SaaS三种云服务区别 2.1第一层叫做IaaS 2.2第二层就是所谓的PaaS 2.3第三层也就是所谓SaaS 三、虚拟化架构 3.1寄居架构 3.2源生架构 3.3操作系统虚拟化架构 3.4混合虚拟化架构 四、虚拟化特点及优势…

服务网关GateWay基础

1. 网关基础介绍1.1 网关是什么1.2 为啥要用网关1.3 常见的网关组件NginxNetflix ZuulSpring Cloud GatewayKongAPISIX综合比较 2. gateWay的使用2.1 springCloud整合gateway2.2 GateWay的相关用法2.3 GateWay路由使用示例基本用法转发/重定向负载请求动态路由 2.5 断言(Predic…

SourceInsight中文编码格式乱码

参考文章&#xff1a;https://blog.csdn.net/m0_53754590/article/details/135594860 file–>Reload as Encoding —>选择编码格式UTF-8&#xff0c;或者GB2312&#xff1b;哪个显示不乱码设置那个&#xff1b; 我这里选择GB2312

线程池 ThreadPoolExecutor 参数详解

一、引言 提到 Java 线程池&#xff0c;就不得不说 ThreadPoolExecutor&#xff0c;它是 Java 并发包 java.util.concurrent 中的一个类&#xff0c;提供一个高效、稳定、灵活的线程池实现&#xff0c;用于实现多线程并发执行任务&#xff0c;提高应用程序的执行效率。 在《任…

【ARM 裸机】C 语言 led 驱动

前面刚学习了汇编 led 驱动的编写和验证&#xff0c;现在开始就要进入 C 语言 led 驱动编写与验证了 ! 1、C 语言运行环境构建 1.1、设置处理器模式 使 6ULL 处于 SVC 模式下&#xff0c;之前已经提到了处理器的九种模式&#xff0c;参考&#xff1a;【ARM 裸机】汇编 led 驱…

Docker 的基本管理

一. 云的相关知识 1. 关于云 云端服务器都有哪些提供商&#xff1a; 国内&#xff1a; 阿里云&#xff08;Alibaba Cloud&#xff09;&#xff1a; 提供ECS&#xff08;Elastic Compute Service&#xff09;弹性计算服务&#xff0c;包括通用型、计算型、内存型等多种实例…

根据图片模板动态生成图片(Java)

根据图片模板生成图片 背景流程简介代码实现 背景 根据提供的证书模板生成对应证书&#xff0c;证书内容有&#xff0c;姓名&#xff0c;身份证号&#xff0c;证书名称&#xff0c;证书编号&#xff0c;发证日期 根据用户达成的条件自动生成证书图片。 证书模板如下&#xff…

【51单片机项目】基于51单片机自制多功能小键盘/模拟USB键盘【附源码】(STC89C52RC+CH9328)

目录 一、效果展示 二、创作灵感 三、硬件电路 注意事项 工作原理 四、源码 main.c 五、附录 CH9328工作原理 CH9328的模式选择 ​编辑 全键盘键码值表 参考链接 一、效果展示 该小键盘具有三种功能&#xff1a; 1、自动输入开机密码 2、每隔一段时间自动按下ct…

多模态大模型训练数据量以及训练方式

多模态大模型系列&#xff1a;LLaVALLaVA1.5/1.6LLaVA-Med - 知乎就在前两天LLaVA 1.6发布了&#xff0c;带来了更大的分辨率&#xff0c;更强的LLM&#xff0c;在最后补充了这一部分的介绍。 LLaVA repo&#xff1a;https://github.com/haotian-liu/LLaVA/ LLaVA 1.0&#xff…

《C语言深度解剖》(9):深度剖析数据在内存中的存储

&#x1f921;博客主页&#xff1a;醉竺 &#x1f970;本文专栏&#xff1a;《C语言深度解剖》 &#x1f63b;欢迎关注&#xff1a;感谢大家的点赞评论关注&#xff0c;祝您学有所成&#xff01; ✨✨&#x1f49c;&#x1f49b;想要学习更多数据结构与算法点击专栏链接查看&am…

操作系统安全:Windows与Linux的安全标识符,身份鉴别和访问控制

「作者简介」&#xff1a;2022年北京冬奥会中国代表队&#xff0c;CSDN Top100&#xff0c;学习更多干货&#xff0c;请关注专栏《网络安全自学教程》 操作系统有4个安全目标&#xff0c;也就是说想要保证操作系统的安全&#xff0c;就必须实现这4个需求&#xff1a; 标识系统…

系统安全与应用(1)

目录 1、账号安全管理 &#xff08;1&#xff09;禁止程序用户登录 &#xff08;2&#xff09;锁定禁用长期不使用的用户 &#xff08;3&#xff09;删除无用的账号 &#xff08;4&#xff09;禁止账号和密码的修改 2、密码安全管理 设置密码有效期 1&#xff09;针对已…