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

这篇具有很好参考价值的文章主要介绍了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_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

HarmonyOS ArkUI实战开发-NAPI 加载原理(下),OpenHarmony,HarmonyOS,移动开发,harmonyos,harmonyOS,移动开发,鸿蒙开发,openharmony,Arkui

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

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

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

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

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

如何快速入门:

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

HarmonyOS ArkUI实战开发-NAPI 加载原理(下),OpenHarmony,HarmonyOS,移动开发,harmonyos,harmonyOS,移动开发,鸿蒙开发,openharmony,Arkui

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

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

HarmonyOS ArkUI实战开发-NAPI 加载原理(下),OpenHarmony,HarmonyOS,移动开发,harmonyos,harmonyOS,移动开发,鸿蒙开发,openharmony,Arkui

基于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. ……

HarmonyOS ArkUI实战开发-NAPI 加载原理(下),OpenHarmony,HarmonyOS,移动开发,harmonyos,harmonyOS,移动开发,鸿蒙开发,openharmony,Arkui

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

HarmonyOS ArkUI实战开发-NAPI 加载原理(下),OpenHarmony,HarmonyOS,移动开发,harmonyos,harmonyOS,移动开发,鸿蒙开发,openharmony,Arkui

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

HarmonyOS ArkUI实战开发-NAPI 加载原理(下),OpenHarmony,HarmonyOS,移动开发,harmonyos,harmonyOS,移动开发,鸿蒙开发,openharmony,Arkui

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

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

HarmonyOS ArkUI实战开发-NAPI 加载原理(下),OpenHarmony,HarmonyOS,移动开发,harmonyos,harmonyOS,移动开发,鸿蒙开发,openharmony,Arkui文章来源地址https://www.toymoban.com/news/detail-859829.html

到了这里,关于HarmonyOS ArkUI实战开发-NAPI 加载原理(下)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • HarmonyOS/OpenHarmony应用开发-ArkTS语言渲染控制LazyForEach数据懒加载

    LazyForEach从提供的数据源中按需迭代数据,并在每次迭代过程中创建相应的组件。当LazyForEach在滚动容器中使用了,框架会根据滚动容器可视区域按需创建组件,当组件划出可视区域外时,框架会进行组件销毁回收以降低内存占用。 一、接口描述 二、IDataSource类型说明 三、

    2024年02月11日
    浏览(43)
  • OpenHarmony 应用 ArkUI 状态管理开发范例

    本文转载自《#2023 盲盒+码 # OpenHarmony 应用 ArkUI 状态管理开发范例》,作者:zhushangyuan_ 本文根据橘子购物应用,实现 ArkUI 中的状态管理。 在声明式 UI 编程框架中,UI 是程序状态的运行结果,用户构建了一个 UI 模型,其中应用的运行时的状态是参数。当参数改变时,UI 作为

    2024年02月10日
    浏览(37)
  • HarmonyOS实战经验合集之ArkUI(二)

    ArkUI(方舟开发框架):是一套UI开发框架,提供开发者进行应用UI开发时所必须的能力。 1)组件:组件是界面搭建与显示的最小单位。开发者通过多种组件的组合,构建出满足自身应用诉求的完整界面。 2)页面:page页面是ArkUI最小的调度分割单位。开发者可以将应用设计为

    2023年04月09日
    浏览(36)
  • 鸿蒙HarmonyOS实战-ArkUI事件(键鼠事件)_ark ui 点击事件

    .onMouse((event: MouseEvent) = { event.stopPropagation(); // 在Button的onMouse事件中设置阻止冒泡 this.buttonText = ‘Button onMouse:n’ + ‘’ + \\\'button = ’ + event.button + ‘n’ + \\\'action = ’ + event.action + ‘n’ + ‘x,y = (’ + event.x + ‘,’ + event.y + ‘)’ + ‘n’ + ‘screenXY=(’ + event.screenX + ‘,’ + eve

    2024年04月16日
    浏览(50)
  • 【HarmonyOS开发】ArkUI实现下拉刷新/上拉加载

     列表下拉刷新、上拉加载更多,不管在web时代还是鸿蒙应用都是一个非常常用的功能,基于ArkUI中TS扩展的声明式开发范式实现一个下拉刷新,上拉加载。 如果数据量过大,可以使用LazyForEach代替ForEach 高阶组件-上拉加载,下拉刷新 https://gitee.com/bingtengaoyu/harmonyos-advanced-com

    2024年01月20日
    浏览(41)
  • HarmonyOS学习路之方舟开发框架—方舟开发框架(ArkUI)概述

    方舟开发框架(简称ArkUI)为HarmonyOS应用的UI开发提供了完整的基础设施,包括简洁的UI语法、丰富的UI功能(组件、布局、动画以及交互事件),以及实时界面预览工具等,可以支持开发者进行可视化界面开发。 UI: 即用户界面。开发者可以将应用的用户界面设计为多个功能

    2024年02月16日
    浏览(49)
  • OpenHarmony鸿蒙原生应用开发,ArkTS、ArkUI学习踩坑学习笔记,持续更新中。

    结论:在BIOS里面将Hyper-V打开,DevEco Studio模拟器可以成功启动。 如果在另外的文件中引用组件,需要使用export导出,并在使用的页面import该自定义组件。 1.自定义组件(被导入组件) 2.组合组件(引用自定义组件) 1、main_pages.json配置文件配置静态路由地址,配置文件

    2024年02月04日
    浏览(80)
  • 【HarmonyOS开发】ArkUI-X 跨平台框架(使用ArkTs开发Android&IOS)

    ArkUI-X 跨平台框架进一步将 ArkUI 开发框架扩展到了多个OS平台,目前支持OpenHarmony、HarmonyOS、Android、 iOS,后续会逐步增加更多平台支持。开发者基于一套主代码,就可以构建支持多平台的精美、高性能应用。 React Native 是一个基于 JavaScript 和 React 的开源框架,由 Facebook 开发和

    2024年01月20日
    浏览(49)
  • 《HarmonyOS开发 – OpenHarmony开发笔记(基于小型系统)》第4章 OpenHarmony应用开发实例

    开发环境 : 开发系统:Ubuntu 20.04 开发板:Pegasus物联网开发板 MCU:Hi3861 OpenHarmony版本:3.0.1-LTS 1.新建工程及源码 新建目录 在applications/sample/myapp中新建src目录以及myapp.c文件,代码如下所示。 新建编译组织文件 新建applications/sample/myapp/BUILD.gn文件,内容如下所示: static_libr

    2024年02月09日
    浏览(82)
  • HarmonyOS 鸿蒙开发DevEco Studio OpenHarmony:创建OpenHarmony工程

    目录 创建和配置新工程 将原子化服务工程改为应用工程 当开始开发一个OpenHarmony应用/服务时,首先需要根据工程创建向导,创建一个新的工程,工具会自动生成对应的代码和资源模板。 说明 在运行DevEco Studio工程时,建议每一个运行窗口有2GB以上的可用内存空间。 通过如下

    2024年01月25日
    浏览(66)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包