原理
ArkTS 和 C++ 引擎被编进同一个动态库 libnativerender.so,通过 NAPI(Node-API) 互调。交互只有两个方向,各靠一张”表”:
| 方向 |
机制 |
入口 |
| TS → C++ |
getContext(type) 返回 C++ 函数表 |
plugin_manager.cpp |
| C++ → TS |
registerFunction 把 JS 函数存入 回调引用表 |
JSRegisterUtils.h / NapiHelper.h |
模块入口:整个库只导出一个 getContext方法
此外,RegisterModule 没有被任何代码显式调用,它带 __attribute__((constructor)),在 libnativerender.so 被加载进进程时自动执行。
加载的触发点是 TS 侧 import ... from 'libnativerender.so',以及 Index.ets 中 XComponent({ libraryname: 'nativerender' })。时序如下:
1 2 3 4 5 6 7
| TS import / XComponent 引用库 ↓ 运行时 dlopen 加载 .so constructor RegisterModule() → napi_module_register(&nativerenderModule) ↓ NAPI 框架回调 nm_register_func Init() → 把 getContext 挂到 exports ↓ TS 得到 exports,可调用 getContext(...)
|
初始化入口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| static napi_value Init(napi_env env, napi_value exports) { napi_property_descriptor desc[] = { DECLARE_NAPI_FUNCTION("getContext", NapiManager::GetContext), }; NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc)); NapiManager::GetInstance()->Export(env, exports); return exports; }
static napi_module nativerenderModule = { .nm_register_func = Init, .nm_modname = "nativerender", };
extern "C" __attribute__((constructor)) void RegisterModule(void) { napi_module_register(&nativerenderModule); }
|
TS → C++(同步调用)
getContext(type) 按传入的枚举返回一组挂着 C++ 实现的函数(一张”分域函数表”),TS 拿到后直接 .xxx() 调用,同步进 C++。
举个例子:
MainAbility.ets中的:
1 2 3 4 5
| import nativeRender from "libnativerender.so"; const rawFileUtils: nativeRender.CPPFunctions = nativeRender.getContext(ContextType.RAW_FILE_UTILS);
rawFileUtils.nativeResourceManagerInit(this.context.resourceManager);
|
C++ 分发实现在 plugin_manager.cpp
凡是 TS 主动调用的 C++ 普通函数,都必须在 NapiManager::GetContext 某个 case 的 desc[] 里用 DECLARE_NAPI_FUNCTION("名字", 实现) 注册,并在 index.d.ts 补一条类型声明。否则 getContext() 返回的对象上根本没有这个方法,TS 侧调不到(运行时 undefined)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
switch (value) { case RAW_FILE_UTILS: { napi_property_descriptor desc[] = { DECLARE_NAPI_FUNCTION("nativeResourceManagerInit", RawFileUtils::nativeResourceManagerInit), }; NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc)/sizeof(desc[0]), desc)); } break; }
|
TS 端的类型声明
1 2 3 4 5 6
| export interface CPPFunctions { nativeResourceManagerInit: (resourceManager: resmgr.ResourceManager) => void; } export const getContext: (a: number) => CPPFunctions;
|
C++ → TS(回调)
原理
启动时 TS 把”名字 → JS 函数”成对注册给 C++,C++ 用 napi_ref 存进一张 map;之后 C++ 随时按名字取出来调用。
注册入口
1 2 3 4 5 6
|
import nativeRender from "libnativerender.so"; const napiContext: nativeRender.CPPFunctions = nativeRender.getContext(ContextType.NATIVE_API); napiContext.nativeEngineStart(); NapiHelper.registerFunctions(napiContext.registerFunction)
|
ts端注册
所有供cpp调用的js方法都在这里:NapiHelper.ts
1 2 3 4 5 6 7 8 9
| export class NapiHelper{ static registerFunctions(registerFunc: Function){ registerFunc('ApplicationManager.exit', ApplicationManager.exit); registerFunc('ApplicationManager.getVersionName', ApplicationManager.getVersionName); registerFunc('DeviceUtils.getDpi', DeviceUtils.getDpi); } }
|
C++ 端注册实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| napi_value registerFunction(napi_env env, napi_callback_info info) { size_t argc = 2; napi_value args[2]; NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, nullptr, nullptr));
napi_ref fucRef; NAPI_CALL(env, napi_create_reference(env, args[1], 1, &fucRef));
char* name = new char[functionName.length() + 1]; strcpy(name, functionName.c_str()); JSFunction* jsFunction = new JSFunction(name, env, fucRef); JSFunction::addFunction(name, jsFunction); return nullptr; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
static std::unordered_map<std::string, JSFunction> FUNCTION_MAP;
static JSFunction getFunction(std::string functionName) { return FUNCTION_MAP.at(functionName); }
template<typename ReturnType, typename... Args> ReturnType invoke(Args... args) { napi_value global; napi_get_global(env, &global); napi_value func; napi_get_reference_value(env, funcRef, &func); napi_value jsArgs[sizeof...(Args)] = {NapiValueConverter::ToNapiValue(env, args)...}; napi_value return_val; napi_call_function(env, global, func, sizeof...(Args), jsArgs, &return_val); ReturnType value; NapiValueConverter::ToCppValue(env, return_val, value); return value; }
|
c++调用
1 2
| std::string ver = JSFunction::getFunction("ApplicationManager.getVersionName").invoke<std::string>();
|
C++ → TS(异步)
napi_call_function 调一个返回 Promise 的 JS 函数时,拿到的是 promise 对象而不是结果,结果要等 .then 回调才有。NapiHelper.h 封装了一套:在后台子线程里把 Promise”拉平”,等到结果后落进一张异步表,供 C++ 之后按 key 取用。注意发起方 invokeAsync 起的是 detach 子线程,它立即返回、不阻塞调用线程;真正阻塞等待的是那个后台子线程。
注意点
- 返回值类型只支持
number/string,字符串 ≤ 64 字节(replyString[64])
- 给 JS 只能传一个字符串入参(或无参)
invokeAsync/getAsyncInfo
invokeAsync / getAsyncInfo 这套在鸿蒙的仓库里其实没有任何业务调用点,initAsyncInfo 还是空实现。
个人此方案觉得不靠谱,invokeAsync后交给js跑异步了。getAsyncInfo需要等异步跑完来获取信息。由于没有回调,也没async/await 异步模型。至于什么时候才算异步跑完,鲁迅没说。全靠猜。
1 2 3 4 5 6 7 8
| enum AsyncInfo { VERSION_NAME = 0, MY_RESULT, LAST_INDEX };
JSFunction::getFunction("Xxx.getXxx").invokeAsync(AsyncInfo::MY_RESULT, "入参");
std::string result = Js_Cocos2dxHelper::getAsyncInfo(AsyncInfo::MY_RESULT);
|
RunPromiseType
1 2 3 4 5 6 7 8 9 10 11
| std::string result;
JSFunction fn = JSFunction::getFunction("Xxx.getXxx");
JSFunction::RunPromiseType(fn.env, fn.funcRef,[&result](std::string value) { result = value; }, "arg");
|
问题
是否所有 native 方法都要加进 GetContext?
是。 凡是 TS 主动调用的 C++ 普通函数,都必须在 NapiManager::GetContext 某个 case 的 desc[] 里用 DECLARE_NAPI_FUNCTION("名字", 实现) 注册,并在 index.d.ts 补一条类型声明——否则 getContext() 返回的对象上不会有这个方法,TS 侧调到的是 undefined。
加到哪个 case 只是按 ContextType 分组:可挂到语义相近的已有 case,也可新增一个枚举值 + 新 case,与功能无关。
三个例外不走这里:
getContext 自身在 napi_init.cpp 顶层 Init 导出,不在 switch 内。
- XComponent 渲染/触摸回调用
OH_NativeXComponent_RegisterCallback 注册,不是 DECLARE_NAPI_FUNCTION。
- C++ → TS 的回调走
registerFunction,属于反方向。