实现ArkTs到Native的跨模块调用module1(ArkTs->Native)->module2(Native->ArkTs->Native)

实现ArkTs到Native的跨模块调用module1(ArkTs->Native)->module2(Native->ArkTs->Native)

HarmonyOS
2024-09-19 11:04:53
浏览
收藏 0
回答 1
待解决
回答 1
按赞同
/
按时间
superinsect

1.使用场景和背景

在大型应用开发中往往是由多个业务组共同完成一个APP的开发,每个业务组都有自己的工程,业务组会以HSP或HAR包的形式提供SDK能力给各个HAP模块使用,这些SDK往往会提供C++接口供其他模块的Native层直接调用,具体如下图:

2.实现方案

整个调用链为module1 ArkTs -> module1 Native -> module2 Native -> module2 ArkTs -> module2 Native。

1. module1 ArkTs调用module1 Native,通过本地Native调用即可。

2. module1 Native调用module2 Native需要将module2的头文件导出,并在module1中添加so依赖,然后通过头文件引用。

3. module2 Native调用module2 ArkTs通过napi_load_module_with_info来加载自定义ets模块,加载时需要env信息,从module1传下来。

4. module2 ArkTs调用module2 Native方法,同1,通过本地Native调用即可。

3.代码实现

下面按照第2章中介绍的4个部分来看具体的代码实现。本文中moudule1是调用方,模块名为app,module2是被调用方,模块名是lib。

3.1 module1 ArkTs调用module1 Native

(1)module1中定义Native方法

在HAP模块中定义invokeModule1Native方法,并在Init中注册。

// module1中的napi_init.cpp 
#include "napi/native_api.h" 
 
// 1. module1中native方法定义 
static napi_value invokeModule1Native(napi_env env, napi_callback_info info) 
{ 
  // todo 调用module2的native方法 
  return nullptr; 
} 
 
EXTERN_C_START 
static napi_value Init(napi_env env, napi_value exports) 
{ 
  napi_property_descriptor desc[] = { 
  // 2. 注册定义的native,用于和ArkTs侧方法绑定 
  { "invokeModule1Native", nullptr, invokeModule1Native, 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 = "app", 
  .nm_priv = ((void*)0), 
  .reserved = { 0 }, 
}; 
 
extern "C" __attribute__((constructor)) void RegisterAppModule(void) 
{ 
  napi_module_register(&demoModule); 
}

(2)在module1中的Index.d.ts中导定义的Native方法。

// module1中Index.d.ts 
export const invokeModule1Native: () => void;

(3)在module1中的Index.ets中调用Native方法。

// module1中的Index.ets 
// 1. 导入so 
import testNapi from 'libapp.so'; 
 
@Entry 
@Component 
struct Index { 
  @State message: string = 'Hello World'; 
  build() { 
    Row() { 
      Column() { 
        Text(this.message) 
          .fontSize(50) 
          .fontWeight(FontWeight.Bold) 
          .onClick(() => { 
            // 2. ArkTs调用本地Native方法 
            testNapi.invokeModule1Native(); 
          }) 
      } 
      .width('100%') 
    } 
    .height('100%') 
  } 
}

3.2 module1 Native调用module2 Native

项目中的跨模块native到native的调用实际就是C++侧调用,和传统的C++调用类似,都是依赖头文件然后调用头文件中的方法,稍微有点区别的是被调用的模块需要申明导出头文件。

在本文场景中,module1是调用模块,module2是被调用的模块。

(1)在被调用模块module2中定义native方法在HSP/HAR包中napi_ini.cpp统计目录下新建napi_ini.h头文件,并定义invokeModule2Native Native方法,在napi_ini.cpp定义实现。

// module2的napi_ini.cpp的同级目录下新建napi_ini.h 
#ifndef ARKTS_RUNTIME_NAPI_INIT_H 
#define ARKTS_RUNTIME_NAPI_INIT_H 
void invokeModule2Native(); 
#endif 
// module2的napi_ini.cpp 
#include "napi/native_api.h" 
#include "napi_init.h" 
 
void invokeModule2Native() 
{ 
  // todo 实现使用napi_load_module_with_info调用ArkTs的业务逻辑 
} 
 
EXTERN_C_START 
static napi_value Init(napi_env env, napi_value exports) { 
  napi_property_descriptor desc[] = { }; 
  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 = "lib", 
  .nm_priv = ((void *)0), 
  .reserved = {0}, 
}; 
 
extern "C" __attribute__((constructor)) void RegisterLibModule(void) { 
  napi_module_register(&demoModule); 
}

(2) 在module2中的build-profile.json5中配置头文件导出。

// module2中的build-profile.json5 
{ 
  "apiType": "stageMode", 
"buildOption": { 
  // ... 
  "nativeLib": { 
    // 配置头文件导出目录 
    "headerPath": "./src/main/cpp" 
  } 
} 
}

(3)在module2的CMakeLists.txt中配置将源文件打包到so。

// module2中的CMakeLists.txt 
// ... 
// 因为源码写在napi_init.cpp了,所以将napi_init.cpp编译到so 
add_library(lib SHARED napi_init.cpp) 
// ...

(4)在module1中的CMakeLists.txt中配置so依赖。

// module1中的CMakeLists.txt 
// ... 
// 配置so依赖,lib::lib中前一个lib是module2的模块名称,后一个lib是module2编译出来的so名称 
target_link_libraries(app PUBLIC libace_napi.z.so lib::lib)

(5)在module1的napi_init.cpp中导入module2的头文件并调用其Native方法。

// module1的napi_init.cpp 
#include "napi/native_api.h" 
#include "napi_init.h" 
 
static napi_value invokeModule1Native(napi_env env, napi_callback_info info) 
{ 
  invokeModule2Native(); 
  return nullptr; 
} 
 
// ...

3.3 module2 Native调用module2 ArkTs

在Native层使用napi方法napi_load_module_with_info加载ets模块,需要napi_env环境变量,而当前场景下是直接从module1的Native调用到module2的Native,所以需要从module1中将napi_env传递到module2中。

(1)在module1的Init方法中传递napi_env

在module2中定义setenv方法,并在module1中的napi_init.cpp中的Init方法中调用setenv方法将module1中的napi_env传递到module2中。

// module2的napi_init.h 
#include "napi/native_api.h" 
 
#ifndef ARKTS_RUNTIME_NAPI_INIT_H 
#define ARKTS_RUNTIME_NAPI_INIT_H 
void invokeModule2Native(); 
void setenv(napi_env env); 
#endif 
 
// module2的napi_init.cpp 
// ... 
void setenv(napi_env env){ 
  g_main_env = env; 
} 
// ... 
// module1的napi_init.cpp 
// ... 
EXTERN_C_START 
static napi_value Init(napi_env env, napi_value exports) 
{ 
  napi_property_descriptor desc[] = { 
  { "invokeModule1Native", nullptr, invokeModule1Native, nullptr, nullptr, nullptr, napi_default, nullptr } 
}; 
napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc); 
 
// 在module1的Init方法中传递napi_env 
setenv(env); 
return exports; 
} 
EXTERN_C_END 
// ...

(2) 实现invokeModule2Native,通过napi_load_module_with_info加载自定义ets并完成调用在module2的ets目录下新建Util.ets。

// module2的../src/main/ets/Util.ets 
export function getFileDir(): string { 
  console.log("test lib get file dir, result" + getContext().filesDir) 
  return getContext().filesDir 
} 
// module2的napi_init.cpp 
// ... 
const char *get_file_dir() 
{ 
  napi_env env = g_main_env; 
  char file_dir[MAX_PATH] = {0}; 
  napi_value module; 
  /** 
   这里napi_load_module_with_info的第2,3个参数要特别注意,第一个参数是待加载的ets文件的路径,第二个参数是bundlename+模块名。 
   如果module2是HSP模块,那么两个参数中的模块名都写HSP模块的module.json5中定义的name即可(下面代码就是以HSP模块为例定义的);而如果是HAR模块,那么2个参数的模块名要写成module1中oh-package.json5中定义的module2的依赖名称,第3个参数的模块名要写成module1的名称(如本文中未app)。此处在第5章中会详细介绍。 
   **/ 
  napi_status status = 
  napi_load_module_with_info(env, "lib/src/main/ets/Util", "com.tencent.demo.my_application/lib", &module); 
  if (napi_ok != status) { 
    return ""; 
  } 
  napi_value fn; 
  status = napi_get_named_property(env, module, "getFileDir", &fn); 
  napi_value path; 
  status = napi_call_function(env, module, fn, 0, nullptr, &path); 
  if (status != napi_ok) { 
    const napi_extended_error_info *info; 
    napi_get_last_error_info(env, &info); 
  } 
  size_t rt_len; 
  napi_get_value_string_utf8(env, path, file_dir, MAX_PATH, &rt_len); 
 
  return file_dir; 
} 
 
void invokeModule2Native() 
{ 
  const char *file_dir = get_file_dir(); 
} 
// ...

3.4 module2 ArkTs调用module2 Native方法。

在module2 ArkTs调用module2 Native方法和3.1中一样,定义native方法并导出,在Util.ets中调用即可。

(1)在module2中定义Native方法并注册。

// module2中的napi_init.cpp 
// ... 
// 1. 定义Native方法add 
static napi_value Add(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); 
 
  napi_valuetype valuetype0; 
  napi_typeof(env, args[0], &valuetype0); 
 
  napi_valuetype valuetype1; 
  napi_typeof(env, args[1], &valuetype1); 
 
  double value0; 
  napi_get_value_double(env, args[0], &value0); 
 
  double value1; 
  napi_get_value_double(env, args[1], &value1); 
 
  napi_value sum; 
  napi_create_double(env, value0 + value1, &sum); 
 
  return sum; 
} 
 
EXTERN_C_START 
static napi_value Init(napi_env env, napi_value exports) { 
  napi_property_descriptor desc[] = { 
    // 2. 注册add方法 
    {"add", nullptr, Add, nullptr, nullptr, nullptr, napi_default, nullptr} 
}; 
napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc); 
return exports; 
} 
EXTERN_C_END 
// ...

(2)导出module2中定义的Native方法。

// module2中的Index.d.ts 
// 在module2中的Index.d.ts中导出add方法 
export const add: (a: number, b: number) => number;

(3)在module2中的Util.ets中调用Native方法。

// module2中的Util.ets 
// 1. 导入so 
import lib from 'liblib.so' 
 
export function getFileDir(): string { 
  console.log("test lib get file dir, result" + getContext().filesDir) 
  // 2.调用Native方法 
  let num = lib.add(1,2); 
  console.log("test lib" + num); 
  return getContext().filesDir 
}

4. 运行效果

5. module2为HSP/HAR时的差异点

module2模块可以是HSP,也可以是HAR,当是HAR时,除了在第3步module2 Native调用module2 ArkTs这里有一些区别外,其他地方都是一样的流程。

当module2是HAR时,在第3步module2 Native调用module2 ArkTs流程中,使用napi_load_module_with_info加载ArkTs模块时,第2,3两个参数的填写需要注意。napi_load_module_with_info的第二个参数表示要加载的ets模块的文件,其中第一个字符串是模块的名称(如lib/src/main/ets/Util中的lib),第二个参数是项目bundlename+入口模块名称(如com.tencent.demo.my_application/lib)。

(1)module2是HAR从上面这个图可以看出HAR是集成到HAP中的,入口模块是HAP,napi_load_module_with_info中第2个参数的模块名称要填HAP模块中oh-package.json5中定义的依赖HAR的名称,而不是HAR模块的实际名称。比如HAP的名称为app,HAR的名称为lib,HAP中定义依赖为:

"dependencies": { 
  "folder": "file:../lib" 
}

那么napi_load_module_with_info的第一个参数应该写为folder/src/main/ets/Util,而不是lib/src/main/ets/Util,第3个参数也应该写为com.tencent.demo.my_application/app,而不是com.tencent.demo.my_application/lib。

(2)module2是HSP,HSP是独立的模块,入口模块就是自己,所以napi_load_module_with_info第2个参数的模块名就是自己的模块名,即lib/src/main/ets/Util,第3个参数的入口模块名也是自己,即com.tencent.demo.my_application/lib。


分享
微博
QQ
微信
回复
2024-09-19 15:56:05
相关问题
Native调用ArkTS侧类函数
966浏览 • 1回复 待解决
HarmonyOS ArkTS调用Native接口机制咨询
454浏览 • 1回复 待解决
Native如何调ArkTS方法
2219浏览 • 1回复 待解决
Native调用ArkTS全局普通方法
919浏览 • 1回复 待解决
arkts侧hashmap转为native
961浏览 • 1回复 待解决
Native Module之间依赖如何配置
990浏览 • 1回复 待解决
HarmonyOS Native 实例化 ArkTS 对象
156浏览 • 1回复 待解决
如何在Native侧释放ArkTS对象
2348浏览 • 1回复 待解决
HarmonyOS ArkTS注册Native C函数监听
352浏览 • 1回复 待解决
ArkTSNative互传数组类型数据
1401浏览 • 1回复 待解决
ArkTSNative如何动态加载、卸载so
1984浏览 • 1回复 待解决
Native侧释放ArkTS对象方法
489浏览 • 1回复 待解决
Native侧进行模块加载
814浏览 • 1回复 待解决
ArkTs怎么传递对象或者类给Native
2360浏览 • 1回复 待解决
ArkTsNative传复杂参数---List参数篇
867浏览 • 1回复 待解决
如何在Native侧构建一个ArkTS对象
2132浏览 • 1回复 待解决
ArkTS中如何使用编译出来Native so库
450浏览 • 1回复 待解决