• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# 使用扩展的Node-API接口在当前线程中创建、切换和销毁上下文环境
2<!--Kit: NDK-->
3<!--Subsystem: arkcompiler-->
4<!--Owner: @xliu-huanwei; @shilei123; @huanghello-->
5<!--Designer: @shilei123-->
6<!--Tester: @kirl75; @zsw_zhushiwei-->
7<!--Adviser: @fang-jinxu-->
8在应用被拉起时,应用的主线程即为一个ArkTS线程,该线程中存在一个由系统管理的上下文环境,当ArkTS需要和C/C++交互时,在C/C++侧,napi_env即代表该上下文环境,每个上下文环境中存在着独立的globalThis对象。开发者可以通过使用Node-API中的扩展接口napi_create_ark_context和napi_destroy_ark_context在当前线程中创建和销毁新的上下文环境,这些新创建的上下文环境和线程中原始的上下文环境共用一个运行时虚拟机。需要注意的是napi_create_ark_context接口仅仅是创建新的上下文环境,而不是创建一个新的运行时,同时通过该接口创建上下文环境,需要通过napi_destroy_ark_context接口销毁,否则会造成内存泄漏。当然ArkTS线程的原始上下文环境不能通过napi_destroy_ark_context接口销毁。当需要切换到指定的上下文环境时,可以调用Node-API中的扩展接口napi_switch_ark_context来切换到指定的上下文环境。开发者可以在一个新的上下文环境中访问globalThis上的某些属性方法,也可以在访问完之后,切回到原先的上下文环境保证上下文环境的隔离。
9
10## 场景介绍
11开发者可以通过napi_create_ark_context接口在当前的线程中创建新的上下文环境,该上下文环境拥有独立的globalThis对象。这意味着当前线程上原始的上下文环境和新创建的上下文环境是相互隔离的,即上下文环境中的globalThis对象不同。开发者可以通过新创建的上下文环境进行模块化加载,加载生成的模块化对象将挂载在当前上下文环境中的globalThis对象上,不同的模块加载在不同的上下文环境中,可以避免一个模块对globalThis对象的上属性的修改影响到另一个模块对globalThis对象上属性的访问。当然部分的Node-API标准接口和扩展接口也进行上下文的适配,这意味着调用这些Node-API的接口,上下文环境会根据接口入参的napi_env来主动进行上下文的切换动作,开发者不需要主动调用napi_switch_ark_context来进行上下文环境的切换,需要注意的是从C++/C侧回到ArkTS侧时,要主动调用napi_switch_ark_context接口将上下文环境切回到对应的上下文环境,否则会造成ArkTS侧的代码执行在另外一个上下文环境导致不可预知的稳定性问题。
12
13## 支持多运行时上下文环境调用的NAPI接口
14下列表格中的NAPI接口都支持在多上下文的环境中执行,其中部分NAPI接口会主动进行上下文的切换,即使调用方没有主动调用napi_switch_ark_context来切换运行时上下文,这部分接口仍然可以通过比较当前运行的上下文环境和接口指定的运行时环境是否一致来决定是否切换上下文环境,如果上下文环境不一致,这些NAPI接口会将当前的运行时环境切换成接口参数指定的上下文环境。当然不涉及主动切换上下文环境的接口意味着这部分接口和运行时上下文无关,使用任意一个有效的上下文环境都行正常执行。
15
16| 接口 | 是否会主动进行上下文切换 |
17| -------- | -------- |
18|napi_module_register | 否 |
19|napi_fatal_error | 否 |
20|napi_async_init | 是 |
21|napi_async_destroy | 否 |
22|napi_make_callback | 是 |
23|napi_create_buffer | 是 |
24|napi_create_external_buffer | 是 |
25|napi_create_buffer_copy | 是 |
26|napi_is_buffer | 否 |
27|napi_get_buffer_info | 否 |
28|napi_create_async_work | 是 |
29|napi_delete_async_work | 否 |
30|napi_queue_async_work | 否 |
31|napi_cancel_async_work | 否 |
32|napi_get_node_version | 否 |
33|napi_fatal_exception | 否 |
34|napi_add_env_cleanup_hook | 否 |
35|napi_remove_env_cleanup_hook | 否 |
36|napi_open_callback_scope | 否 |
37|napi_close_callback_scope | 否 |
38|napi_create_threadsafe_function | 是 |
39|napi_get_threadsafe_function_context | 否 |
40|napi_call_threadsafe_function | 否 |
41|napi_acquire_threadsafe_function | 否 |
42|napi_release_threadsafe_function | 否 |
43|napi_unref_threadsafe_function | 否 |
44|napi_ref_threadsafe_function | 否 |
45|napi_add_async_cleanup_hook | 否 |
46|napi_remove_async_cleanup_hook | 否 |
47node_api_get_module_file_name | 否 |
48|napi_get_last_error_info | 否 |
49|napi_get_undefined | 否 |
50|napi_get_null | 否 |
51|napi_get_global | 否 |
52|napi_get_boolean | 否 |
53|napi_create_object | 否 |
54|napi_create_array | 否 |
55|napi_create_array_with_length | 否 |
56|napi_create_double | 否 |
57|napi_create_int32 | 否 |
58|napi_create_uint32 | 否 |
59|napi_create_int64 | 否 |
60|napi_create_string_latin1 | 否 |
61|napi_create_string_utf8 | 否 |
62|napi_create_string_utf16 | 否 |
63|napi_create_symbol | 否 |
64|napi_create_function | 是 |
65|napi_create_error | 是 |
66|napi_create_type_error | 是 |
67|napi_create_range_error | 是 |
68|napi_typeof | 否 |
69|napi_get_value_double | 否 |
70|napi_get_value_int32 | 否 |
71|napi_get_value_uint32 | 否 |
72|napi_get_value_int64 | 否 |
73|napi_get_value_bool | 否 |
74|napi_get_value_string_latin1 | 否 |
75|napi_get_value_string_utf8 | 否 |
76|napi_get_value_string_utf16 | 否 |
77|napi_coerce_to_bool | 否 |
78|napi_coerce_to_number | 是 |
79|napi_coerce_to_object | 是 |
80|napi_coerce_to_string | 是 |
81|napi_get_prototype | 是 |
82|napi_get_property_names | 是 |
83|napi_set_property | 是 |
84|napi_has_property | 是 |
85|napi_get_property | 是 |
86|napi_delete_property | 是 |
87|napi_has_own_property | 是 |
88|napi_set_named_property | 是 |
89|napi_has_named_property | 是 |
90|napi_get_named_property | 是 |
91|napi_set_element | 是 |
92|napi_has_element | 是 |
93|napi_get_element | 是 |
94|napi_delete_element | 是 |
95|napi_define_properties | 是 |
96|napi_is_array | 否 |
97|napi_get_array_length | 否 |
98|napi_strict_equals | 否 |
99|napi_call_function | 否 |
100|napi_new_instance | 是 |
101|napi_instanceof | 是 |
102|napi_get_cb_info | 否 |
103|napi_get_new_target | 否 |
104|napi_define_class | 是 |
105|napi_wrap | 是 |
106|napi_unwrap | 是 |
107|napi_remove_wrap | 是 |
108|napi_create_external | 否 |
109|napi_get_value_external | 否 |
110|napi_create_reference | 否 |
111|napi_delete_reference | 否 |
112|napi_reference_ref | 否 |
113|napi_reference_unref | 否 |
114|napi_get_reference_value | 否 |
115|napi_open_handle_scope | 否 |
116|napi_close_handle_scope | 否 |
117|napi_open_escapable_handle_scope | 否 |
118|napi_close_escapable_handle_scope | 否 |
119|napi_escape_handle | 否 |
120|napi_throw | 否 |
121|napi_throw_error | 是 |
122|napi_throw_type_error | 是 |
123|napi_throw_range_error | 是 |
124|napi_is_error | 否 |
125|napi_is_exception_pending | 否 |
126|napi_get_and_clear_last_exception | 否 |
127|napi_is_arraybuffer | 否 |
128|napi_create_arraybuffer | 否 |
129|napi_create_external_arraybuffer | 否 |
130|napi_get_arraybuffer_info | 否 |
131|napi_is_typedarray | 否 |
132|napi_create_typedarray | 否 |
133|napi_get_typedarray_info | 否 |
134|napi_create_dataview | 是 |
135|napi_is_dataview | 否 |
136|napi_get_dataview_info | 否 |
137|napi_get_version | 否 |
138|napi_create_promise | 是 |
139|napi_resolve_deferred | 否 |
140|napi_reject_deferred | 否 |
141|napi_is_promise | 否 |
142|napi_run_script | 否 |
143|napi_create_date | 是 |
144|napi_is_date | 否 |
145|napi_get_date_value | 否 |
146|napi_add_finalizer | 否 |
147|napi_create_bigint_int64 | 否 |
148|napi_create_bigint_uint64 | 否 |
149|napi_create_bigint_words | 是 |
150|napi_get_value_bigint_int64 | 否 |
151|napi_get_value_bigint_uint64 | 否 |
152|napi_get_value_bigint_words | 否 |
153|napi_get_all_property_names | 否 |
154|napi_set_instance_data | 否 |
155|napi_get_instance_data | 否 |
156|napi_detach_arraybuffer | 否 |
157|napi_is_detached_arraybuffer | 否 |
158|napi_type_tag_object | 否 |
159|napi_check_object_type_tag | 是 |
160|napi_object_freeze | 是 |
161|napi_object_seal | 是 |
162|napi_run_script_path | 是 |
163|napi_queue_async_work_with_qos | 否 |
164|napi_load_module | 是 |
165|napi_create_object_with_properties | 是 |
166|napi_create_object_with_named_properties | 是 |
167|napi_coerce_to_native_binding_object | 是 |
168|napi_load_module_with_info | 是 |
169|napi_create_ark_runtime | 否 |
170|napi_destroy_ark_runtime | 否 |
171|napi_serialize | 是 |
172|napi_deserialize | 是 |
173|napi_delete_serialization_data | 否 |
174|napi_call_threadsafe_function_with_priority | 否 |
175|napi_wrap_enhance | 是 |
176
177## 不支持多运行时上下文环境调用的NAPI接口
178| 接口 | 多运行时上下文环境调用返回值 |
179| -------- | -------- |
180|napi_define_sendable_class | napi_invalid_arg |
181|napi_is_sendable | napi_invalid_arg |
182|napi_create_sendable_object_with_properties | napi_invalid_arg |
183|napi_wrap_sendable | napi_invalid_arg |
184|napi_wrap_sendable_with_size | napi_invalid_arg |
185|napi_unwrap_sendable | napi_invalid_arg |
186|napi_remove_wrap_sendable | napi_invalid_arg |
187|napi_create_sendable_array | napi_invalid_arg |
188|napi_create_sendable_array_with_length | napi_invalid_arg |
189|napi_create_sendable_arraybuffer | napi_invalid_arg |
190|napi_create_sendable_typedarray | napi_invalid_arg |
191|napi_run_event_loop | napi_invalid_arg |
192|napi_stop_event_loop | napi_invalid_arg |
193|napi_get_uv_event_loop | napi_invalid_arg |
194
195### 示例代码
196- 模块注册
197    ```c++
198    // napi_init.cpp
199    #include "napi/native_api.h"
200    #include "hilog/log.h"
201
202    static napi_value NAPI_Global_callFunctionInContext(napi_env env, napi_callback_info info)
203    {
204        napi_status status = napi_ok;
205        size_t argc = 1;
206        napi_value args[1] = {nullptr};
207        if (napi_get_cb_info(env, info, &argc, args, nullptr, nullptr) != napi_ok) {
208            return nullptr;
209        }
210        // 在原始上下文中加载模块plugin1.ets
211        napi_value plugin1 = nullptr;
212        status = napi_load_module_with_info(env, "entry/src/main/ets/pages/plugin1", "com.example.myapplication/entry", &plugin1);
213        if (status != napi_ok) {
214            OH_LOG_INFO(LOG_APP, "load plugin1 failed");
215        }
216        // 获取模块plugin1中的方法GetLocation
217        napi_value getLocation1 = nullptr;
218        status = napi_get_named_property(env, plugin1, "GetLocation", &getLocation1);
219        if (status != napi_ok) {
220            OH_LOG_INFO(LOG_APP, "obtain GetLocation from plugin1 failed");
221        }
222        // 创建新的上下文环境newEnv2
223        napi_env newEnv2 = nullptr;
224        status = napi_create_ark_context(env, &newEnv2);
225        if (status != napi_ok) {
226            return nullptr;
227        }
228        // 主动切换到新的上下文环境newEnv2
229        status = napi_switch_ark_context(newEnv2);
230        if (status != napi_ok) {
231            OH_LOG_INFO(LOG_APP, "switch to newEnv2 failed");
232        }
233        napi_value plugin2 = nullptr;
234        // 在新的上下文环境中加载模块plugin2.ets
235        status = napi_load_module_with_info(newEnv2, "entry/src/main/ets/pages/plugin2", "com.example.myapplication/entry",
236                                            &plugin2);
237        if (status != napi_ok) {
238            OH_LOG_INFO(LOG_APP, "load plugin2 failed");
239        }
240
241        napi_value getLocation2 = nullptr;
242        status = napi_get_named_property(newEnv2, plugin2, "GetLocation", &getLocation2);
243        if (status != napi_ok) {
244            OH_LOG_INFO(LOG_APP, "obtain GetLocation from plugin2 failed");
245        }
246
247        // 在新上下文环境中执行ArkTS侧的方法getLocation, 入参为模块plugin2中的方法GetLocation
248        napi_value result = nullptr;
249        napi_value args2[1] = {};
250        args2[0] = getLocation2;
251
252        status = napi_call_function(newEnv2, nullptr, args[0], 1, args2, &result);
253        if (status != napi_ok) {
254            OH_LOG_INFO(LOG_APP, "call function of env failed");
255        }
256        int32_t ret = 0;
257        status = napi_get_value_int32(newEnv2, result, &ret);
258        if (status != napi_ok) {
259            OH_LOG_INFO(LOG_APP, "napi_get_value_int32 of env failed");
260        } else {
261            // plugin2的上下文中globalThis.a为3000
262            OH_LOG_INFO(LOG_APP, "ret is %{public}d", ret); // 3000
263        }
264        // 主动切回原始上下文环境env
265        status = napi_switch_ark_context(env);
266        if (status != napi_ok) {
267            OH_LOG_INFO(LOG_APP, "switch to env failed");
268        }
269        args2[0] = getLocation1;
270        status = napi_call_function(env, nullptr, args[0], 1, args2, &result);
271        if (status != napi_ok) {
272            return nullptr;
273        }
274        // 获取GetLocation接口调用之后的返回值
275        ret = 0;
276        status = napi_get_value_int32(env, result, &ret);
277        if (status != napi_ok) {
278            return nullptr;
279        } else {
280            // plugin1的上下文中globalThis.a为2000
281            OH_LOG_INFO(LOG_APP, "ret is %{public}d", ret); // 2000
282        }
283        // 销毁创建的上下文环境
284        status = napi_destroy_ark_context(newEnv2);
285        if (status != napi_ok) {
286            return nullptr;
287        }
288        return result;
289    }
290
291    // 模块注册
292    EXTERN_C_START
293    static napi_value Init(napi_env env, napi_value exports) {
294        napi_property_descriptor desc[] = {
295            {"callFunctionInContext", nullptr, NAPI_Global_callFunctionInContext,
296                nullptr, nullptr, nullptr, napi_default, nullptr}
297        };
298        napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
299        return exports;
300    }
301    EXTERN_C_END
302
303    static napi_module demoModule = {
304        .nm_version = 1,
305        .nm_flags = 0,
306        .nm_filename = nullptr,
307        .nm_register_func = Init,
308        .nm_modname = "entry",
309        .nm_priv = ((void*)0),
310        .reserved = { 0 },
311    };
312
313    extern "C" __attribute__((constructor)) void RegisterEntryModule(void)
314    {
315        napi_module_register(&demoModule);
316    }
317    ```
318- 接口声明
319    ```ts
320    // index.d.ts
321    export const callFunctionInContext: (func: (func:()=>number)=>{}) => number;
322    ```
323
324- 编译配置
3251. CMakeLists.txt文件需要按照如下配置
326    ```
327    // CMakeLists.txt
328    # the minimum version of CMake.
329    cmake_minimum_required(VERSION 3.5.0)
330    project(MyApplication8)
331
332    set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})
333
334    if(DEFINED PACKAGE_FIND_FILE)
335        include(${PACKAGE_FIND_FILE})
336    endif()
337
338    add_definitions( "-DLOG_DOMAIN=0xd0d0" )
339    add_definitions( "-DLOG_TAG=\"testTag\"")
340
341    include_directories(${NATIVERENDER_ROOT_PATH}
342                        ${NATIVERENDER_ROOT_PATH}/include)
343
344    add_library(entry SHARED napi_init.cpp)
345    target_link_libraries(entry PUBLIC libace_napi.z.so libhilog_ndk.z.so)
346    ```
347
3482. 需要在工程的build-profile.json5文件中进行以下配置
349    ```json
350    {
351        "buildOption" : {
352            "arkOptions" : {
353                "runtimeOnly" : {
354                    "sources": [
355                        "./src/main/ets/pages/plugin1.ets",
356                        "./src/main/ets/pages/plugin2.ets"
357                    ]
358                }
359            }
360        }
361    }
362    ```
363
364- ArkTS代码示例
365    ```ts
366    // index.ets
367    import testNapi from "libentry.so"
368    // 该接口用于执行模块plugin1或plugin2中的GetLocation方法
369    function getLocation(func: () => number) {
370        return func();
371    }
372    testNapi.callFunctionInContext(getLocation)
373    ```
374    ```ts
375    // ets/pages/plugin1.ets
376    globalThis.a = 2000;
377
378    export function GetLocation() : number {
379        return globalThis.a;
380    }
381    ```
382    ```ts
383    // ets/pages/plugin2.ets
384    globalThis.a = 3000;
385
386    export function GetLocation() : number {
387    return globalThis.a;
388    }
389    ```