NAPI调用失败常见问题处理
==============

#### 问题现象

通过NAPI调用C函数失败并报错。例如：发生Cpp Crash或者后台崩溃。

Native侧调用ArkTS侧函数报错napi\_function\_expected。

#### 背景知识

[HarmonyOS Node-API](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/using-napi-interaction-with-cpp)是基于Node.js 12.x LTS的Node-API规范扩展开发的机制，为开发者提供了ArkTS/JS与C/C++模块之间的交互能力。它提供了一组稳定的、跨平台的API，可以在不同的操作系统上使用。

#### 场景一：跨线程使用错误

#### \[h2\]问题定位

*   通过hilog日志检索关键字“Fatal”，分析错误日志判断报错类型。
*   排查异步调用流程，不能通过napi\_call\_function调用ArkTS函数。

#### \[h2\]分析结论

*   常见报错信息如：Fatal: ecma\_vm cannot run in multi-thread，该错误是由于env不能跨线程使用。
*   如果涉及异步调用，避免在非主线程中调用。因napi\_call\_function需要在主线程（即JS线程）执行，并且参数env信息也是主线程的信息，不能跨线程使用。

#### \[h2\]修改建议

*   回调函数必须运行在JS的主线程中，其他线程发起调用会抛出异常，可以参考[线程安全函数](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/use-napi-thread-safety)。
*   异步调用需要在主线程中进行。使用napi\_call\_function方法在Node-API模块中对ArkTS侧函数进行调用时，确保传入的argv的参数数量必须与声明的argc一致，且被初始化成nullptr。

#### 场景二：函数调用错误

#### \[h2\]问题定位

*   排查ArkTS侧调用Native侧函数时的参数传递。
*   排查ArkTS侧被调用的函数是否使用export关键字导出。
*   排查Native侧回调函数实现。

#### \[h2\]分析结论

*   确保在调用NAPI函数时，传递的参数类型和数量与函数定义一致。
*   ArkTS侧被调用函数未使用export关键字导出。
*   确保在ArkTS端注册的回调函数实现正确，并且在需要时能够正确调用。

#### \[h2\]修改建议

*   使用napi\_get\_cb\_info接口获取有关函数调用的参数信息和this指针，确保参数正确。
*   调用ArkTS侧函数时，ArkTS侧函数需要使用export关键字导出，示例代码如下：
    *   napi\_init.cpp：
        
        #include "napi/native\_api.h"
        
        static napi\_value LoadModule(napi\_env env, napi\_callback\_info info) 
        {
            napi\_value result;
           _ // 1. 使用napi\_load\_module\_with\_info加载模块_
            napi\_load\_module\_with\_info(env, "entry/src/main/ets/pages/Index", "com.example.callback/entry", &result);
            napi\_value testFn;
           _ // 2. 使用napi\_get\_named\_property获取test函数_
            napi\_get\_named\_property(env, result, "test", &testFn);
        
            napi\_value inputArgs\[2\];
            int32\_t a = 5, b = 4;
            napi\_create\_int32(env, a, &inputArgs\[0\]);
            napi\_create\_int32(env, b, &inputArgs\[1\]);
        
           _ // 3. 使用napi\_call\_function调用函数test_
            napi\_value output;
            napi\_call\_function(env, result, testFn, 2, inputArgs, &output);
        
            return output;
        }
        
        EXTERN\_C\_START
        static napi\_value Init(napi\_env env, napi\_value exports) 
        {
            napi\_property\_descriptor desc\[\] = {
                {"loadModule", nullptr, LoadModule, 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 = "entry",
            .nm\_priv = ((void \*)0),
            .reserved = {0},
        };
        
        extern "C" \_\_attribute\_\_((constructor)) void RegisterEntryModule(void) { napi\_module\_register(&demoModule); }
        
    *   Index.d.ts：
        
        export const loadModule:()=>any;
        
    *   entry/src/main/ets/pages/Index.ets：
        
        import testNapi from 'libentry.so';
        
        export function test(a:number,b:number){
          let c = a - b;
          console.log("%d - %d = %d", a, b, c);
        }
        
        @Entry
        @Component
        struct Index {
          @State message: string = 'Hello World';
        
          build() {
            Row() {
              Column() {
                Text(this.message)
                  .fontSize($r('app.float.page\_text\_font\_size'))
                  .fontWeight(FontWeight.Bold)
                  .onClick(() => {
                    this.message = 'Welcome';
                    testNapi.loadModule();
                  })
              }
              .width('100%')
            }
            .height('100%')
          }
        }
        

*   使用napi\_create\_function方法将C/C++函数包装为可在ArkTS中调用的函数，并返回一个表示该函数的napi\_value。

#### 场景三：文件引用错误

#### \[h2\]问题定位

检查开发环境配置以及头文件和库文件的引入。

#### \[h2\]分析结论

1.  项目中缺少相关依赖库。
2.  CMakeLists.txt脚本中遗漏了编译所需的源代码、头文件以及三方库等。

#### \[h2\]修改建议

1.  检查开发环境是否配置完善，包括安装了必要的依赖库（如libuv）。
2.  确保CMakeLists.txt脚本中添加了编译所需的源代码、头文件以及三方库等。CMakeLists.txt脚本示例如下：
    
    _\# the minimum version of CMake._
    cmake\_minimum\_required(VERSION 3.5.0)
    project(S\_20250329170715728896)
    
    _\# 定义一个变量，并赋值为当前模块cpp目录_
    set(NATIVERENDER\_ROOT\_PATH ${CMAKE\_CURRENT\_SOURCE\_DIR})
    
    _\# 添加头文件.h目录，包括cpp，cpp/include，告诉cmake去这里找到代码引入的头文件_
    include\_directories(${NATIVERENDER\_ROOT\_PATH}
                        ${NATIVERENDER\_ROOT\_PATH}/include)
    
    _\# 声明一个产物libentry.so，SHARED表示产物为动态库，hello.cpp为产物的源代码_
    add\_library(entry SHARED napi\_init.cpp)
    
    _\# 声明产物entry链接时需要的三方库libace\_napi.z.so_
    _\# 这里直接写三方库的名称是因为它是在ndk中，已在链接寻址路径中，无需额外声明_
    target\_link\_libraries(entry PUBLIC libace\_napi.z.so)