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

发布于:2025-08-14 ⋅ 阅读:(17) ⋅ 点赞:(0)

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值中提取number
    double 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++处理逻辑:每个元素乘以2
        double 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) {
    // 注册所有函数到exports
    return exports;  // 返回填充后的exports对象
}// 3. ArkTS中可以使用 import hello from 'libhello.so'

内存管理:

// C++函数包装器的生命周期由V8 GC管理
// 当JavaScript中没有引用时,包装器会被垃圾回收
// 但C++动态库保持加载状态直到应用退出

这个注册机制的核心是建立JavaScript世界和C++世界之间的桥梁,通过NAPI提供的标准接口,实现类型安全的双向通信。每个注册的函数都会在JavaScript运行时中创建对应的包装器,确保调用的正确性和性能。


网站公告

今日签到

点亮在社区的每一天
去签到