HarmonyOS NDK的JavaScript/TypeScript与C++交互机制

HarmonyOS NDK的JavaScript/TypeScript与C++交互机制

细解释这个调用流程:
整体架构流程

ArkTS/JavaScript ←→ .d.ts (类型定义) ←→ NAPI ←→ .cpp (C++实现)
  1. 文件结构和作用
    项目结构示例:
MyHarmonyApp/
├── entry/src/main/ets/          # ArkTS应用代码
│   └── pages/Index.ets
├── entry/src/main/cpp/          # C++原生代码
│   ├── CMakeLists.txt
│   ├── hello.cpp               # C++实现
│   └── types/libhello/         # 类型定义
│       └── index.d.ts          # TypeScript类型声明
└── oh-package.json5
  1. .d.ts类型定义文件
    .d.ts文件作用:

为C++函数提供TypeScript类型声明
定义JavaScript和C++之间的接口
IDE智能提示和类型检查

示例 - index.d.ts:

// entry/src/main/cpp/types/libhello/index.d.ts
export const add: (a: number, b: number) => number;
export const getString: () => string;
export const processArray: (arr: number[]) => number[];// 复杂对象交互
export interface UserInfo {name: string;age: number;scores: number[];
}export const processUser: (user: UserInfo) => UserInfo;
export const asyncOperation: (callback: (result: string) => void) => void;
  1. C++实现文件
    hello.cpp实现:
#include "napi/native_api.h"
#include <string>
#include <vector>// 简单数值计算
static napi_value Add(napi_env env, napi_callback_info info) {size_t argc = 2;napi_value args[2] = {nullptr};// 获取JavaScript传入的参数napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);// 从JavaScript值中提取numberdouble value1, value2;napi_get_value_double(env, args[0], &value1);napi_get_value_double(env, args[1], &value2);// C++计算double result = value1 + value2;// 将C++结果转换为JavaScript值返回napi_value jsResult;napi_create_double(env, result, &jsResult);return jsResult;
}// 字符串处理
static napi_value GetString(napi_env env, napi_callback_info info) {std::string cppString = "Hello from C++!";napi_value jsString;napi_create_string_utf8(env, cppString.c_str(), cppString.length(), &jsString);return jsString;
}// 数组处理
static napi_value ProcessArray(napi_env env, napi_callback_info info) {size_t argc = 1;napi_value args[1] = {nullptr};napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);// 获取数组长度uint32_t arrayLength;napi_get_array_length(env, args[0], &arrayLength);// 创建结果数组napi_value resultArray;napi_create_array_with_length(env, arrayLength, &resultArray);// 处理每个元素for (uint32_t i = 0; i < arrayLength; i++) {napi_value element;napi_get_element(env, args[0], i, &element);double value;napi_get_value_double(env, element, &value);// C++处理逻辑:每个元素乘以2double processedValue = value * 2;napi_value processedElement;napi_create_double(env, processedValue, &processedElement);napi_set_element(env, resultArray, i, processedElement);}return resultArray;
}// 复杂对象处理
static napi_value ProcessUser(napi_env env, napi_callback_info info) {size_t argc = 1;napi_value args[1] = {nullptr};napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);napi_value userObj = args[0];// 提取对象属性napi_value nameValue, ageValue, scoresValue;napi_get_named_property(env, userObj, "name", &nameValue);napi_get_named_property(env, userObj, "age", &ageValue);napi_get_named_property(env, userObj, "scores", &scoresValue);// 转换为C++类型size_t nameLength;char name[100];napi_get_value_string_utf8(env, nameValue, name, 100, &nameLength);double age;napi_get_value_double(env, ageValue, &age);// C++处理逻辑std::string processedName = "Processed: " + std::string(name);double processedAge = age + 1;// 创建返回对象napi_value result;napi_create_object(env, &result);napi_value newName, newAge;napi_create_string_utf8(env, processedName.c_str(), processedName.length(), &newName);napi_create_double(env, processedAge, &newAge);napi_set_named_property(env, result, "name", newName);napi_set_named_property(env, result, "age", newAge);napi_set_named_property(env, result, "scores", scoresValue); // 原样返回数组return result;
}// 模块初始化和函数注册
EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports) {// 将C++函数注册为JavaScript可调用的函数napi_property_descriptor desc[] = {{"add", nullptr, Add, nullptr, nullptr, nullptr, napi_default, nullptr},{"getString", nullptr, GetString, nullptr, nullptr, nullptr, napi_default, nullptr},{"processArray", nullptr, ProcessArray, nullptr, nullptr, nullptr, napi_default, nullptr},{"processUser", nullptr, ProcessUser, nullptr, nullptr, nullptr, napi_default, nullptr},};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_modname = "hello", // 模块名称.nm_priv = ((void*)0),.reserved = { 0 },
};// 注册模块
extern "C" __attribute__((constructor)) void RegisterHelloModule(void) {napi_module_register(&demoModule);
}
  1. CMakeLists.txt配置

# entry/src/main/cpp/CMakeLists.txt
cmake_minimum_required(VERSION 3.4.1)
project(hello)set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})
set(NAPI_ROOT_PATH ${NATIVERENDER_ROOT_PATH}/../../../../../../)# 添加头文件路径
include_directories(${NAPI_ROOT_PATH}${NAPI_ROOT_PATH}/third_party/node/src)# 编译动态库
add_library(hello SHARED hello.cpp)# 链接系统库
target_link_libraries(hello PUBLIC libace_napi.z.so libc++.a)
  1. ArkTS中调用C++函数
    Index.ets使用示例:
// entry/src/main/ets/pages/Index.ets
import hello from 'libhello.so'; // 导入编译后的动态库@Entry
@Component
struct Index {@State result: number = 0;@State message: string = '';@State processedArray: number[] = [];build() {Column() {// 简单函数调用Button('调用C++加法').onClick(() => {this.result = hello.add(10, 20); // 调用C++函数console.log('C++计算结果:', this.result); // 30})Text(`计算结果: ${this.result}`)// 字符串函数调用Button('获取C++字符串').onClick(() => {this.message = hello.getString();})Text(`C++消息: ${this.message}`)// 数组处理Button('处理数组').onClick(() => {let inputArray = [1, 2, 3, 4, 5];this.processedArray = hello.processArray(inputArray);})Text(`处理后数组: ${JSON.stringify(this.processedArray)}`)// 复杂对象交互Button('处理用户对象').onClick(() => {let user: UserInfo = {name: "张三",age: 25,scores: [85, 92, 78]};let processedUser = hello.processUser(user);console.log('处理后的用户:', processedUser);})}.width('100%').height('100%')}
}// 引入类型定义
interface UserInfo {name: string;age: number;scores: number[];
}
  1. 编译和构建流程
    构建命令:

# 1. 清理构建缓存
hvigor clean# 2. 编译C++代码为动态库
hvigor assembleHap --mode module -p product=default# 构建过程:
# .cpp → (CMake) → libhello.so → 打包到HAP → 运行时加载
  1. 调用时序图
用户点击按钮↓
ArkTS: hello.add(10, 20)↓
HarmonyOS Runtime 加载 libhello.so↓
NAPI Bridge: 参数类型转换 (JS → C++)↓
C++: Add函数执行计算↓
NAPI Bridge: 返回值转换 (C++ → JS)↓
ArkTS: 接收结果并更新UI
  1. 异步调用示例
    C++异步函数:
#include <thread>
#include <chrono>static napi_value AsyncOperation(napi_env env, napi_callback_info info) {size_t argc = 1;napi_value args[1] = {nullptr};napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);napi_value callback = args[0];// 创建异步工作std::thread([env, callback]() {// 模拟耗时操作std::this_thread::sleep_for(std::chrono::seconds(2));// 调用JavaScript回调napi_value result;napi_create_string_utf8(env, "异步操作完成!", 15, &result);napi_value global;napi_get_global(env, &global);napi_call_function(env, global, callback, 1, &result, nullptr);}).detach();return nullptr;
}

ArkTS调用异步函数:


Button('异步操作').onClick(() => {hello.asyncOperation((result: string) => {console.log('异步结果:', result);// 更新UI});})

这个交互机制让HarmonyOS应用能够充分利用C++的性能优势,同时保持ArkTS的开发便利性。通过NAPI桥接层,实现了类型安全的双向通信。

详细解析这个HarmonyOS NDK模块初始化和函数注册的核心机制:

  1. EXTERN_C_START/END 宏解析
    宏定义原理:
// 在napi头文件中定义
#ifdef __cplusplus#define EXTERN_C_START extern "C" {#define EXTERN_C_END   }
#else#define EXTERN_C_START#define EXTERN_C_END
#endif

作用机制:


// 展开后实际代码:
extern "C" {static napi_value Init(napi_env env, napi_value exports) {// 函数实现}
}// 原理说明:
// 1. 阻止C++名称修饰 (Name Mangling)
// 2. 确保函数名在符号表中保持原始形式
// 3. 让JavaScript运行时能够准确找到函数入口点

名称修饰对比:

// 没有extern "C"的情况:
// C++编译器生成: _Z4InitP8napi_envP10napi_value
// JavaScript运行时无法找到此符号// 有extern "C"的情况:
// 编译器生成: Init
// JavaScript运行时可以直接找到"Init"符号
  1. napi_property_descriptor 结构详解
    完整结构定义:

typedef struct {const char* utf8name;        // 属性名称napi_value name;            // 属性名称的napi_value版本napi_callback method;       // 函数指针napi_callback getter;       // getter函数napi_callback setter;       // setter函数napi_value value;          // 属性值napi_property_attributes attributes;  // 属性特性void* data;               // 用户数据指针
} napi_property_descriptor;
  1. 注册流程深入分析
    Step 1: 内存布局准备
// 编译时,desc数组在栈上分配
napi_property_descriptor desc[4];  // 4个函数描述符// 内存布局:
// [desc[0]: "add"函数描述]
// [desc[1]: "getString"函数描述]  
// [desc[2]: "processArray"函数描述]
// [desc[3]: "processUser"函数描述]

Step 2: napi_define_properties 内部机制

// HarmonyOS运行时内部伪代码
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++) {const napi_property_descriptor* prop = &properties[i];// 1. 为每个C++函数创建JavaScript函数包装器napi_value js_function;napi_create_function(env, prop->utf8name,     // "add"NAPI_AUTO_LENGTH,prop->method,       // Add函数指针prop->data,&js_function);// 2. 将JavaScript函数添加到exports对象napi_set_named_property(env, object, prop->utf8name, js_function);}return napi_ok;
}

Step 3: 函数包装器创建

// 运行时为每个C++函数创建包装器
// 伪代码表示包装器结构
struct FunctionWrapper {napi_callback cpp_function;     // 指向Add函数napi_env environment;          // 运行环境void* user_data;              // 用户数据// JavaScript调用时的入口点napi_value CallCppFunction(napi_env env, napi_callback_info info) {// 1. 参数类型检查和转换// 2. 调用实际的C++函数return cpp_function(env, info);// 3. 返回值类型转换}
};
  1. exports对象机制
    exports对象的本质:

// exports就是一个JavaScript对象
// 类似于:
var exports = {add: function(a, b) { return [调用C++ Add函数]; },getString: function() { return [调用C++ GetString函数]; },processArray: function(arr) { return [调用C++ ProcessArray函数]; },processUser: function(user) { return [调用C++ ProcessUser函数]; }
};

注册后的内存结构:

exports对象在V8堆中的结构:
{"add": FunctionWrapper {callback: &Add,env: current_env,data: nullptr},"getString": FunctionWrapper {callback: &GetString,env: current_env, data: nullptr},// ... 其他函数
}
  1. 完整调用链路分析
    JavaScript调用C++的完整流程:
// 1. ArkTS调用
hello.add(10, 20)// 2. V8引擎查找exports["add"]
JSFunction* js_func = exports.GetProperty("add");// 3. 调用函数包装器
FunctionWrapper* wrapper = js_func.GetWrapper();// 4. 参数类型转换 (JS → NAPI)
napi_value args[2];
args[0] = JSNumberToNapiValue(10);
args[1] = JSNumberToNapiValue(20);// 5. 调用实际C++函数
napi_value result = wrapper->cpp_function(env, callback_info);// 6. 返回值类型转换 (NAPI → JS)  
JSValue js_result = NapiValueToJSNumber(result);// 7. 返回给ArkTS
return js_result; // 30
  1. 错误处理和类型安全
    运行时类型检查:

// NAPI内部会进行类型验证
static napi_value Add(napi_env env, napi_callback_info info) {size_t argc = 2;napi_value args[2] = {nullptr};// 获取参数napi_status status = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);if (status != napi_ok) {// 抛出JavaScript异常napi_throw_error(env, nullptr, "参数获取失败");return nullptr;}// 类型检查napi_valuetype type0, type1;napi_typeof(env, args[0], &type0);napi_typeof(env, args[1], &type1);if (type0 != napi_number || type1 != napi_number) {napi_throw_type_error(env, nullptr, "参数必须是数字类型");return nullptr;}// 继续处理...
}
  1. 模块生命周期管理
    初始化时机:

// 1. 动态库加载时 (dlopen)
extern "C" __attribute__((constructor)) void RegisterHelloModule(void) {napi_module_register(&demoModule);
}// 2. HarmonyOS运行时调用Init函数
static napi_value Init(napi_env env, napi_value exports) {// 注册所有函数到exportsreturn exports;  // 返回填充后的exports对象
}// 3. ArkTS中可以使用 import hello from 'libhello.so'

内存管理:

// C++函数包装器的生命周期由V8 GC管理
// 当JavaScript中没有引用时,包装器会被垃圾回收
// 但C++动态库保持加载状态直到应用退出这个注册机制的核心是建立JavaScript世界和C++世界之间的桥梁,通过NAPI提供的标准接口,实现类型安全的双向通信。每个注册的函数都会在JavaScript运行时中创建对应的包装器,确保调用的正确性和性能。

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

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

相关文章

[激光原理与应用-226]:机械 - 如何学习3D图设计

学习机械领域的3D图设计需要系统掌握软件操作、设计思维、工程规范和实战经验。以下是分阶段的学习路径和实用建议&#xff0c;帮助你高效入门并提升技能&#xff1a;一、基础准备阶段1. 明确学习目标方向选择&#xff1a;根据兴趣确定细分领域&#xff08;如机械零件设计、钣金…

uniapp -- 小程序处理与设备通讯 GBK/GB2312 编码问题。

🌐 小程序/UniApp 中处理 GBK 编码:iconv-lite + Buffer 实用指南 适用场景:设备通信、蓝牙传输、旧系统对接、十六进制转中文等涉及 GB2312/GBK 编码 的中文乱码问题。 🧩 一、为什么需要这个工具? 在小程序或 UniApp 开发中,常遇到以下问题: 蓝牙设备返回的中文是 …

8.13 JavaWeb(MySQL P89-P103)

DML&#xff08;数据操作语言&#xff09;Data Manipulation Language&#xff0c;用来对数据库表中的数据记录进行增、删、改操作添加数据-- DML &#xff1a; 数据操作语言 -- DML &#xff1a; 插入数据 - insert -- 1.为tb_emp表的username&#xff0c;name&#xff0c;gen…

Python 类元编程(元类基础知识)

元类基础知识 元类是制造类的工厂&#xff0c;不过不是函数&#xff08;如示例 21-2 中的 record_factory&#xff09;&#xff0c;而是类。图 21-1 使用机器和小怪兽图示法描述元 类&#xff0c;可以看出&#xff0c;元类是生产机器的机器。根据 Python 对象模型&#xff0c;类…

【Vue 3 响应式系统深度解析:reactive vs ref 全面对比】

Vue 3 响应式系统深度解析&#xff1a;reactive vs ref 全面对比 目录 概述响应式系统基础reactive 深度分析ref 深度分析底层实现原理依赖收集机制演进解构和转换工具常见误区和陷阱技术选型指南最佳实践和建议 概述 Vue 3 引入了基于 Proxy 的全新响应式系统&#xff0c;…

JavaSE高级-01

文章目录1. 异常异常的分类自定义异常异常的处理资源关闭&#xff1a;try-with-resource2. 泛型泛型类泛型接口泛型方法、通配符、上下限通配符泛型的上下限泛型支持的类型3. 包装类4. Collection集合和Map集合4.1 Collection集合Collection集合特点Collection的遍历方式一&…

MyBatis执行器与ORM特性深度解析

一、MyBatis的Executor执行器详解1. MyBatis执行器类型MyBatis有三种核心执行器实现&#xff0c;在org.apache.ibatis.executor包中定义&#xff1a;执行器类型特点描述SimpleExecutor默认执行器&#xff0c;每次执行都会创建新的Statement对象ReuseExecutor重用预处理语句(Pre…

红黑树的特性与实现

在数据结构领域&#xff0c;二叉搜索树&#xff08;BST&#xff09;凭借 O (log n) 的平均时间复杂度成为查找、插入和删除操作的优选结构。但它有个致命缺陷&#xff1a;当输入数据有序时&#xff0c;会退化为链表&#xff0c;时间复杂度骤降至 O (n)。为解决这一问题&#xf…

ClickHouse从入门到企业级实战全解析课程简介

【课程简介】你是否正在面临这些挑战&#xff1f;海量数据的分析查询慢如蜗牛&#xff0c;报表一等就是几小时&#xff1f;想构建实时数仓&#xff0c;却不知如何高效处理 Kafka 等流式数据&#xff1f;对 ClickHouse 的众多 MergeTree 引擎感到困惑&#xff0c;不知如何选型&a…

【新启航】从人工偏差到机械精度:旋转治具让三维扫描重构数据重复精度提升至 ±0.01mm

在三维扫描重构领域&#xff0c;传统人工操作方式受限于人为因素干扰&#xff0c;数据重复精度难以保证&#xff0c;无法满足高精度工业检测与逆向工程需求。旋转治具凭借先进的机械设计与自动化控制技术&#xff0c;将三维扫描重构数据重复精度提升至 0.01mm&#xff0c;实现从…

《汇编语言:基于X86处理器》第13章 复习题和编程练习

本篇记录了《汇编语言&#xff1a;基于X86处理器》第13章 复习题和编程练习的学习笔记。13.6 复习题1.当汇编过程被高级语言程序调用时&#xff0c;主调程序与被调过程是否应使用相同的内存模式?答&#xff1a;主调程序与被调过程使用的内存模式必须相同。2.C 和 C程序调用汇编…

SpringAI智能航空助手实战<Demo>

我们将如何将我们得传统业务进行智能化的改造>>>1.将我们传统的航空票务系统 我们之前通过按钮的方式来完成 现在我们通过智能对话的方式完成 >现在我们通过对话的方式来完成 整个智能化的改造 传统应用如何进行智能化改造 我们把我们的项目通过Spring-ai 来接入A…

windows git安装步骤

1&#xff0c;从官网下载安装包&#xff1a;gitg官网 进行安装 2&#xff0c;配置git环境&#xff1a; git config --global user.name "Your Name" git config --global user.email "Your Email"3&#xff0c;生成 SSH Key&#xff1a; ssh-keygen -t r…

使用chroma和LlamaIndex做RAG增强

RAG 原理&#xff1a;通过 “检索&#xff08;从知识库获取相关信息&#xff09;→ 增强&#xff08;将信息作为上下文输入模型&#xff09;→ 生成&#xff08;模型基于上下文回答&#xff09;” 三步&#xff0c;解决大模型知识时效性、领域局限性问题。 接下来将完成这么一个…

2025 最应避免的摄影陷阱以及解决方案

你有没有想过&#xff0c;当你拍完了一个完美的场景后&#xff0c;却发现画面模糊、光线不足&#xff0c;或者更糟的是&#xff0c;存储卡中的文件丢失了&#xff1f;这些问题可能会发生在任何人身上&#xff0c;无论是业余爱好者、专业人士还是最好的摄影师。当珍贵的记忆变成…

python类--python011

面向对象编程中的类的概念、属性使用、继承和类的改造问题等。7.1 初识类在软件编程中&#xff0c;面向过程和面向对象是两种主要的编程方法。面向过程的编程强调通过函数来实现特定的功能&#xff0c;具有灵活性&#xff0c;但在复杂系统中往往导致代码重复&#xff0c;维护困…

Python函数篇:从零到精通

一、函数1.1 为什么有函数我们对于一个项目时&#xff0c;会有上千甚至上万条代码&#xff0c;当我们要使用到某个函数时&#xff0c;例如我需要计算一个求和代码&#xff0c;获得求和的值来服务我们的项目&#xff0c;那我们可能会这样#计算1&#xff5e;100的和 theSun 0 fo…

QT项目之记事本

本文用QT实现记事本功能。一、成品展示1.界面主要元素&#xff1a;1.标题为MyNoteBook&#xff1b;2.相应图标为&#xff1a;打开文件&#xff0c;保存&#xff0c;退出&#xff1b;3.右下角标注光标所在行列&#xff0c;默认编码方式为UTF-8&#xff1b;4.鼠标所在图标位置时会…

【软件测试】性能测试 —— 工具篇 JMeter 介绍与使用

&#x1f970;&#x1f970;&#x1f970;来都来了&#xff0c;不妨点个关注叭&#xff01; &#x1f449;博客主页&#xff1a;欢迎各位大佬!&#x1f448; 文章目录1. JMeter 的介绍2. JMeter 安装、配置、搭建2.1 前置条件 —— Java环境搭建2.2 JMeter 下载2.3 JMeter 安装…

二十二、Mybatis-快速入门程序

入门程序大概步骤叙述&#xff1a; 步骤一&#xff1a;创建springboot工程并且数据库提前创建表步骤二&#xff1a;创建springboot工程对Mybatis相关依赖注意打勾步骤三&#xff1a;编写查找方法步骤四&#xff1a;编写测试方法项目目录结构与数据库以及代码&#xff1a; 项目目…