HarmonyOS NDK的JavaScript/TypeScript与C++交互机制
HarmonyOS NDK的JavaScript/TypeScript与C++交互机制
细解释这个调用流程:
整体架构流程
ArkTS/JavaScript ←→ .d.ts (类型定义) ←→ NAPI ←→ .cpp (C++实现)
- 文件结构和作用
项目结构示例:
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
- .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;
- 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);
}
- 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)
- 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. 清理构建缓存
hvigor clean# 2. 编译C++代码为动态库
hvigor assembleHap --mode module -p product=default# 构建过程:
# .cpp → (CMake) → libhello.so → 打包到HAP → 运行时加载
- 调用时序图
用户点击按钮↓
ArkTS: hello.add(10, 20)↓
HarmonyOS Runtime 加载 libhello.so↓
NAPI Bridge: 参数类型转换 (JS → C++)↓
C++: Add函数执行计算↓
NAPI Bridge: 返回值转换 (C++ → JS)↓
ArkTS: 接收结果并更新UI
- 异步调用示例
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模块初始化和函数注册的核心机制:
- 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"符号
- 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;
- 注册流程深入分析
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. 返回值类型转换}
};
- 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},// ... 其他函数
}
- 完整调用链路分析
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
- 错误处理和类型安全
运行时类型检查:
// 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. 动态库加载时 (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运行时中创建对应的包装器,确保调用的正确性和性能。