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

news2024/12/27 11:17:24

上一节笔者给大家讲解了 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_END

static 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;
        // 有值且值为0
        property.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引擎侧的字符串值赋值给propertyName
    Local<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都是为null
    if (propertyDescriptor.getter != nullptr || propertyDescriptor.setter != nullptr) {

        // 省略部分代码……

    } else if (propertyDescriptor.method != nullptr) { // 本样例中method非空,配置的是C++端对应的方法名
        fullName += propertyDescriptor.utf8name;
        // 调用 NapiNativeCreateFunction方法创建一个 JS 引擎侧的方法cbObj
        Local<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.coloradmin.cn/o/1617179.html

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

相关文章

使用d3.js画一个BoxPlot

Box Plot 在画Box Plot之前&#xff0c;先来了解下Box Plot是什么&#xff1f; 箱线图&#xff08;Box Plot&#xff09;也称盒须图、盒式图或箱型图&#xff0c;是一种用于展示数据分布特征的统计图表。 它由以下几个部分组成&#xff1a; 箱子&#xff1a;表示数据的四分…

【圆桌论坛】个人作为嘉宾参与问答环节的总结,Create 2024百度AI开发者大会之AI智能体开发与应用论坛

目录 ⭐前言⭐讨论话题✨本质和价值✨端侧部署✨应用商业模式✨商业模式 ⭐主题总结⭐有趣分享 ⭐前言 首先&#xff0c;非常荣幸和开心作为开发者和创业者代表参加百度Create AI大会分论坛圆桌论坛的问答环节。 在分论坛活动开始前&#xff0c;参加了文心智能体平台&#xff…

【iOS】类与对象底层探索

文章目录 前言一、编译源码二、探索对象本质三、objc_setProperty 源码探索四、类 & 类结构分析isa指针是什么类的分析元类元类的说明 五、著名的isa走位 & 继承关系图六、objc_class & objc_objectobjc_class结构superClassbitsclass_rw_tclass_ro_tro与rw的区别c…

【无法运行 AutoCAD,原因可能如下1) 此版本的 AutoCAD 装不正确】

错误提示如下 打开autoremove&#xff0c;点击扩展&#xff0c;输入 无法运行&#xff0c;点击搜索 如果出现这个提示&#xff0c;请重启电脑再点击一遍此按钮 出现修复成功即可 ​如果没提示修复成功可以联系技术人员。 ps&#xff1a;autoremove每周六登录方式用其他登…

警惕侵权风险,张驰课堂六西格玛培训产品请认准官方平台购买

亲爱的朋友们&#xff1a; 在知识经济的时代&#xff0c;知识产权的保护显得尤为重要。我们深知&#xff0c;知识是创新的源泉&#xff0c;而知识产权则是保障创新成果得以合理运用的重要法律手段。然而&#xff0c;近期我们公司&#xff08;张驰课堂&#xff09;却遭遇了一起…

Web3革命:区块链如何重塑互联网

引言 互联网的发展已经深刻地改变了我们的生活方式&#xff0c;而现在&#xff0c;Web3和区块链技术正在为我们提供一个全新的数字世界的视角。本文将带你深入了解Web3的核心概念、技术特性以及它如何正在重塑我们的互联网体验。 从Web1.0到Web3&#xff1a;数字革命的演进 W…

C++ 面向对象-封装

C 是一种多范式编程语言&#xff0c;它支持面向对象编程&#xff08;OOP&#xff09;范式。面向对象编程是一种程序设计思想&#xff0c;其中程序由对象组成&#xff0c;每个对象都是一个实例&#xff0c;具有数据和相关操作。在C中&#xff0c;实现面向对象编程主要通过类和对…

物理机中没有VMNet1和VMNet8虚拟网卡

控制面板——网络连接——网络适配器 VMware Network Adapter VMnet1 VMware Network Adapter VMnet8 如果没有这两个虚拟网卡&#xff0c;虚拟机的网络会出现问题 # 解决办法-恢复虚拟网卡默认设置 1、下载并打开ccleaner&#xff0c;ccleaner官网&#xff1a;CCleaner M…

jdbc操作数据库 and 一个商品管理页面

文章目录 1. 介绍1.1 应用知识介绍1.2 项目介绍 2. 文件目录2.1 目录2.2 介绍以下&#xff08;从上到下&#xff09; 3. 相关代码3.1 DBConnection.java3.2 MysqlUtil.java3.3 AddServlet.java3.4 CommodityServlet.java3.5 DelectServlet.java3.6 SelectByIdServlet.java3.7 S…

2024 OceanBase 开发者大会:OceanBase 4.3正式发布,打造PB级实时分析数据库

4月20日&#xff0c;2024 OceanBase开发者大会盛大召开&#xff0c;吸引了50余位业界知名的数据库专家和爱好者&#xff0c;以及来自全国各地的近600名开发者齐聚一堂。他们围绕一体化、多模、TP与AP融合等前沿技术趋势展开深入讨论&#xff0c;分享场景探索的经验和最佳实践&a…

同旺科技 USB TO SPI / I2C适配器读写24LC256--字节写

所需设备&#xff1a; 1、USB 转 SPI I2C 适配器&#xff1b;内附链接 2、24LC256芯片 适应于同旺科技 USB TO SPI / I2C适配器升级版、专业版&#xff1b; 00地址写入一个字节数据AA&#xff0c;并读回验证&#xff1b; 单字节写时序&#xff1a; 读字节时序&#xff1a; …

架构师系列-MYSQL调优(七)- 索引单表优化案例

索引单表优化案例 1. 建表 创建表 插入数据 下面是一张用户通讯表的表结构信息,这张表来源于真实企业的实际项目中,有接近500万条数据. CREATE TABLE user_contacts (id INT(11) NOT NULL AUTO_INCREMENT,user_id INT(11) DEFAULT NULL COMMENT 用户标识,mobile VARCHAR(50…

2016年新华三杯复赛实验试题

2016年新华三杯复赛实验试题 拓扑图 配置需求 考生根据以下配置需求在 HCL 中的设备上进行相关配置。 以太网接口配置 将 S1、S2 的以太网接口 G1/0/1 至 G1/0/16 的模式用命令 combo enable copper 激活为电口。 虚拟局域网 为了减少广播&#xff0c;需要规划并配置 VLA…

Zabbix监控系统:基础配置及部署代理服务器

目录 前言 一、自定义监控内容 1、在客户端创建自定义key 2、在服务端验证新建的监控项 3、在web界面创建自定义监控项模版 3.1 创建模版 3.2 创建应用集&#xff08;用于管理监控项&#xff09; 3.3 创建监控项 3.4 创建触发器 3.5 创建图形 3.6 将主机与模板关联…

利用selenium发挥vip残存的价值

历史版本谷歌浏览器驱动下载地址 https://chromedriver.storage.googleapis.com/index.html 找到与你电脑当前谷歌浏览器版本一致的驱动然后下载下来(大版本一致即可)。我本地版本是 99.0.04844.51 我这里把 chromedriver 放到 /usr/local/bin 下面了。 启动测试窗口 这里需要…

【软件测试】认识测试|测试岗位|软件测试和开发的区别|优秀的测试人员需要具备的素质

一、什么是测试 测试在⽣活中处处可⻅ 1.生活中的测试场景 案例⼀&#xff1a;对某款购物软件进⾏测试 *启动测试&#xff1a;点击软件图标&#xff0c;测试软件是否可以正常打开 搜索测试&#xff1a;点击输入框&#xff0c;输入关键词&#xff0c;点击搜索 商品测试&#…

【Linux】IO多路转接技术Epoll的使用

【Linux】IO多路转接技术Epoll的使用 文章目录 【Linux】IO多路转接技术Epoll的使用前言正文接口介绍工作原理LT模式与ET模式边缘触发&#xff08;ET&#xff09;水平触发&#xff08;LT&#xff09; 理解ET模式和非阻塞文件描述符ET模式epoll实现TCP服务器简单地封装epoll系统…

python创建线程和结束线程

&#x1f47d;发现宝藏 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。【点击进入巨牛的人工智能学习网站】。 python创建线程和结束线程 在 Python 中&#xff0c;线程是一种轻量级的执行单元&#xff…

【C++学习】STL之空间配置器之一级空间配置器

文章目录 &#x1f4ca;什么是空间配置器✈STL 提供六大组件的了解&#x1f440;为什么需要空间配置器&#x1f44d;SGI-STL空间配置器实现原理&#x1f302;一级空间配置器的实现 &#x1f4ca;什么是空间配置器 空间配置器&#xff0c;顾名思义就是为各个容器高效的管理空间…

“五之链”第十六期沙龙活动在呆马科技成功举办

2024年4月19日&#xff0c;由临沂呆码区块链网络科技有限公司&#xff08;呆马科技&#xff09;承办的第十六期“五之链”物流主题沙龙活动成功举办。此次活动邀请了政府相关部门、知名科研院所、物流企业等20余家单位参与&#xff0c;共同探讨物流数据要素流通与智能应用的发展…