如何实现ArkTS到Native的跨模块长调用
=======================

#### 问题现象

整个调用链为hap ArkTS->har ArkTS->har Native侧->har ArkTS。设定hap模块为entry。

1.  entry模块ArkTS调用har ArkTS侧A.ets页面导出的接口。
2.  har ArkTS侧A.ets页面中接口调用har Native侧的方法。
3.  har Native侧方法通过napi\_load\_module\_with\_info来加载har ArkTS侧B.ets导出的自定义接口。

![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/dd/v3/FX_zAxBRSuWzsFhuc-a8tw/zh-cn_image_0000002659258289.png?HW-CC-KV=V1&HW-CC-Date=20260709T133835Z&HW-CC-Expire=86400&HW-CC-Sign=7D93391D5957B87662921CC2C8FE1F5737F78F27C601409D334FF6CB39027B78 "点击放大")

har的ArkTS侧将函数包裹在命名空间中导出，其Native侧用napi\_get\_named\_property加载函数，加载失败。

问题代码示例参考如下：

_// har__包的ObjectUtil.ets_
namespace  ObjectUtil{
  export function testOne(){
    console.info('你好，世界！')
  }
}

export {ObjectUtil}

_// har包的napi\_init.cpp_
static napi\_value loadModule(napi\_env env, napi\_callback\_info info) {
    napi\_value result;
 _   // 1.使用napi\_load\_module\_with\_info加载Test文件中的模块_
    napi\_status status \= napi\_load\_module\_with\_info(env, "mytest\_sdk/src/main/ets/common/ObjectUtil",
                                                    "com.example.sodemo/mytest\_sdk", &result);
    if (status != napi\_ok) {
        return nullptr;
    }
    napi\_value testFn;
   _ // 2.使用napi\_get\_named\_property获取test函数_
    status \= napi\_get\_named\_property(env, result, "testOne", &testFn); _// 获取失败，status：napi\_object\_expected_
    if (status != napi\_ok) {
        return nullptr;
    }
 _   // 3.使用napi\_call\_function调用函数test_
    status \= napi\_call\_function(env, result, testFn, 0, nullptr, nullptr);
    if (status != napi\_ok) {
        return nullptr;
    }
    return result;
}

#### 背景知识

应用开发中往往是由多个业务组共同完成一个APP的开发，每个业务组都有自己的工程，业务组会以HSP或har包的形式提供SDK能力给各个hap模块使用，这些SDK往往会提供C++接口或ets接口供其他模块直接调用。har包构建参考[如何构建har包并导出Native方法](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/har-package#导出native方法)。

Node-API中的[napi\_load\_module\_with\_info](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/use-napi-load-module-with-info#napi_load_module_with_info支持的场景)接口的功能是进行模块的加载，当模块加载出来之后，可以使用函数napi\_get\_property获取模块导出的变量，也可以使用napi\_get\_named\_property获取模块导出的函数。

napi\_status napi\_load\_module\_with\_info(napi\_env env,
                                       const char\* path,
                                       const char\* module\_info,
                                       napi\_value\* result);

| 
参数

 | 

说明

 |
| :-- | :-- |
| 

env

 | 

当前的虚拟机环境。

 |
| 

path

 | 

加载的文件路径或者模块名。

 |
| 

module\_info

 | 

bundleName/moduleName的路径拼接。

 |
| 

result

 | 

加载的模块。

 |

#### 解决方案

1.  har的ArkTS侧导出接口时包裹了一层namespace，Native侧在加载时需要用napi\_get\_named\_property()先获取到命名空间对象，再去获取函数对象（一共获取两次），无法直接获取函数。
2.  har是集成到hap中的，其入口模块是hap，即entry模块。所以napi\_load\_module\_with\_info中：
    *   path：加载文件路径或者moduleName。注意此处的moduleName指的是入口模块（即entry模块）中oh-package.json5中dependencies标签下为har定义的名称，而不是har模块的实际名称。
        
        hap（即entry模块）的oh-package.json5：
        
        {
          "name": "entry",
          "version": "1.0.0",
          "description": "Please describe the basic information.",
          "main": "",
          "author": "",
          "license": "",
          "dependencies": {
            "mytest\_sdk": "file:../mytest\_sdk"
          }
        }
        
        如以上示例，若依赖中配置是"lib\_mytest\_sdk": "file:../mytest\_sdk"，则path参数中的moduleName为lib\_mytest\_sdk，而非mytest\_sdk。即path：“lib\_mytest\_sdk/src/main/ets/common/ObjectUtil”，非“mytest\_sdk/src/main/ets/common/ObjectUtil”。
        
    *   module\_info：bundleName/moduleName的路径拼接。此处的moduleName指的是被加载的模块（har）所在的hap下module.json5中module节点的name属性。
        
        hap（即entry模块）的module.json5：
        
        {
          "module": {
            "name": "entry",
            "type": "entry",
            "description": "$string:module\_desc",
            "mainElement": "EntryAbility",
            "deviceTypes": \[
              "phone"
            \],
            "deliveryWithInstall": true,
            "installationFree": false,
            "pages": "$profile:main\_pages",
            "abilities": \[
              {
                "name": "EntryAbility",
                "srcEntry": "./ets/entryability/EntryAbility.ets",
                "description": "$string:EntryAbility\_desc",
                "icon": "$media:layered\_image",
                "label": "$string:EntryAbility\_label",
                "startWindowIcon": "$media:startIcon",
                "startWindowBackground": "$color:start\_window\_background",
                "exported": true,
                "skills": \[
                  {
                    "entities": \[
                      "entity.system.home"
                    \],
                    "actions": \[
                      "ohos.want.action.home"
                    \]
                  }
                \]
              }
            \],
            "extensionAbilities": \[
              {
                "name": "EntryBackupAbility",
                "srcEntry": "./ets/entrybackupability/EntryBackupAbility.ets",
                "type": "backup",
                "exported": false,
                "metadata": \[
                  {
                    "name": "ohos.extension.backup",
                    "resource": "$profile:backup\_config"
                  }
                \],
              }
            \]
          },
        
        如上图所示，module\_info参数中的moduleName为入口模块的模块名entry，而非har的模块名mytest\_sdk。module\_info：com.example.sodemo/entry，非com.example.sodemo/mytest\_sdk。
        
3.  完整示例参考如下：
    
    har包（即mytest\_sdk模块）的napi\_init.cpp：
    
    static napi\_value TestCall(napi\_env env, napi\_callback\_info info)
    {
        size\_t argc \= 2;
        napi\_value args\[2\] \= {nullptr};
        napi\_get\_cb\_info(env, info, &argc, args, nullptr, nullptr);
        
        size\_t len1 \= 0; 
        napi\_get\_value\_string\_utf8(env, args\[0\], nullptr, 0, &len1); _// Get string length to len_ 
        char\* path \= new char\[len1+1\];                                   _// Allocate a char array of appropriate size_
        napi\_get\_value\_string\_utf8(env, args\[0\], path, len1 + 1, &len1); _// get string_ 
        
        size\_t len2 \= 0; 
        napi\_get\_value\_string\_utf8(env, args\[1\], nullptr, 0, &len2);   _// Get string length to len_ 
        char\* moduleInfo \= new char\[len2+1\];                                   _// Allocate a char array of appropriate size_
        napi\_get\_value\_string\_utf8(env, args\[1\], moduleInfo, len2 + 1, &len2); _// get string_ 
        
        OH\_LOG\_INFO(LOG\_APP, "Receive：path\[%{public}s\], module\_info:\[%{public}s\]", path,moduleInfo);
        
        napi\_value result;
      _  // 1. 使用napi\_load\_module\_with\_info加载Test文件中的模块_
        napi\_status status \= napi\_load\_module\_with\_info(env, path,moduleInfo, &result);   
        if (status != napi\_ok) {
            OH\_LOG\_INFO(LOG\_APP, "napi\_load\_module\_with\_info加载Test文件中的模块 失败: %{public}d", status);
            return nullptr;
        }
        napi\_value testFn;
        napi\_value testNamespace;
       _ // 2. 使用napi\_get\_named\_property获取命名空间对象_
        status \= napi\_get\_named\_property(env, result, "ObjectUtil", &testNamespace);
        if (status != napi\_ok) {
            OH\_LOG\_INFO(LOG\_APP, "napi\_get\_named\_property获取命名空间对象 失败: %{public}d", status);
            return nullptr;
        }
      _  // 2.1 使用napi\_get\_named\_property获取testTwo函数_
        status \= napi\_get\_named\_property(env, testNamespace, "testOne", &testFn);
        if (status != napi\_ok) {
            OH\_LOG\_INFO(LOG\_APP, "napi\_get\_named\_property获取testTwo函数 失败: %{public}d", status);
            return nullptr;
        }
      _  // 3. 使用napi\_call\_function调用函数test_
        status \= napi\_call\_function(env, testNamespace, testFn, 0, nullptr, nullptr);
        if (status != napi\_ok) {
            OH\_LOG\_INFO(LOG\_APP, "napi\_call\_function调用函数test 失败: %{public}d", status);
            return nullptr;
        }
        napi\_value ret;
        std::string str \= "TestCall";
        napi\_create\_string\_utf8(env, str.c\_str(), str.size(), &ret);
        return ret;
    }