• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Native API在应用工程中的使用指导
2
3OpenHarmony的应用必须用js来桥接native。需要使用ace_napi仓中提供的napi接口来处理js交互。napi提供的接口名与三方Node.js一致,目前支持部分接口,符号表见仓下的该文件`libnapi.ndk.json`。
4
5## 开发流程:
6
7IDE中会包含使用Native API的默认工程,使用`File`->`New`->`Create Project`创建`Native C++`模板工程。创建后在`main`目录下会包含`cpp`目录,可以使用ace_napi仓下提供的napi接口进行开发。
8
9js侧通过`import`引入native侧包含处理js逻辑的so,如:`import hello from 'libhello.so'`,意为使用libhello.so的能力,native侧通过napi接口创建的js对象会给到应用js侧的`hello`对象。
10
11## 开发建议:
12
13### 1、注册建议
14
15* nm_register_func对应的函数需要加上static,防止与其他so里的符号冲突。
16* 模块注册的入口,即使用\_\_attribute\_\_((constructor))修饰的函数的函数名需要确保不与其他模块重复。
17### 2、so命名规则
18
19* 每个模块对应一个so
20* 如模块名为`hello`,则so的名字为`libhello.so`,`napi_module`中`nm_modname`字段应为`hello`,大小写与模块名保持一致,应用使用时写作:`import hello from 'libhello.so'`
21
22### 3、js对象线程限制
23
24ark引擎会对js对象线程使用进行保护,不正确使用会引起应用crash。
25
26* napi接口只能在js线程使用。
27* env与线程绑定,不能跨线程使用。native侧js对象只能在创建时的线程使用,即与线程所持有的env绑定。
28
29### 4. napi_create_async_work接口说明
30
31napi_create_async_work里有两个回调:
32
33* execute:用于异步处理业务逻辑。因为不在JS线程中,所以不允许调用napi的接口。业务逻辑的返回值可以返回到complete回调中处理。
34
35* complete:可以调用napi的接口,将execute中的返回值封装成JS对象返回。此回调在JS线程中执行。
36
37```c++
38napi_status napi_create_async_work(napi_env env,
39                                   napi_value async_resource,
40                                   napi_value async_resource_name,
41                                   napi_async_execute_callback execute,
42                                   napi_async_complete_callback complete,
43                                   void* data,
44                                   napi_async_work* result)
45```
46
47
48
49## 示例一,storage 模块——同步异步接口封装
50
51### 模块简介
52
53本例通过实现 `storage` 模块展示了同步和异步方法的封装。`storage ` 模块实现了数据的保存、获取、删除、清除功能。
54
55### 接口声明
56
57```typescript
58import { AsyncCallback } from './basic';
59declare namespace storage {
60  function get(key: string, callback: AsyncCallback<string>): void;
61  function get(key: string, defaultValue: string, callback: AsyncCallback<string>): void;
62  function get(key: string, defaultValue?: string): Promise<string>;
63  function set(key: string, value: string, callback: AsyncCallback<string>): void;
64  function remove(key: string, callback: AsyncCallback<void>): void;
65  function clear(callback: AsyncCallback<void>): void;
66  function getSync(key: string, defaultValue?: string): string;
67  function setSync(key: string, value: string): void;
68  function removeSync(key: string): void;
69  function clearClear(): void;
70}
71export default storage;
72```
73
74
75
76### 具体实现
77
78完整代码参见仓下路径:`sample/native_module_storage/`
79
80#### 模块注册
81
82如下,注册了4个同步接口(`getSync`、`setSync`、`removeSync`、`clearSync`)、4个异步接口(`get`、`set`、`remove`、`clear`)。
83
84```c++
85/***********************************************
86 * Module export and register
87 ***********************************************/
88static napi_value StorgeExport(napi_env env, napi_value exports)
89{
90    napi_property_descriptor desc[] = {
91        DECLARE_NAPI_FUNCTION("get", JSStorageGet),
92        DECLARE_NAPI_FUNCTION("set", JSStorageSet),
93        DECLARE_NAPI_FUNCTION("remove", JSStorageDelete),
94        DECLARE_NAPI_FUNCTION("clear", JSStorageClear),
95
96        DECLARE_NAPI_FUNCTION("getSync", JSStorageGetSync),
97        DECLARE_NAPI_FUNCTION("setSync", JSStorageSetSync),
98        DECLARE_NAPI_FUNCTION("deleteSync", JSStorageDeleteSync),
99        DECLARE_NAPI_FUNCTION("clearSync", JSStorageClearSync),
100    };
101    NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc));
102    return exports;
103}
104
105// storage module
106static napi_module storage_module = {.nm_version = 1,
107                                     .nm_flags = 0,
108                                     .nm_filename = nullptr,
109                                     .nm_register_func = StorgeExport,
110                                     .nm_modname = "storage",
111                                     .nm_priv = ((void*)0),
112                                     .reserved = {0}};
113
114// storage module register
115extern "C" __attribute__((constructor)) void StorageRegister()
116{
117    napi_module_register(&storage_module);
118}
119```
120
121#### getSync 函数实现
122
123如上注册时所写,`getSync` 对应的函数是 `JSStorageGetSync` 。从 `gKeyValueStorage` 中获取数据后,创建一个字符串对象并返回。
124
125```c
126static napi_value JSStorageGetSync(napi_env env, napi_callback_info info)
127{
128    GET_PARAMS(env, info, 2);
129    NAPI_ASSERT(env, argc >= 1, "requires 1 parameter");
130    char key[32] = {0};
131    size_t keyLen = 0;
132    char value[128] = {0};
133    size_t valueLen = 0;
134
135    // 参数解析
136    for (size_t i = 0; i < argc; i++) {
137        napi_valuetype valueType;
138        napi_typeof(env, argv[i], &valueType);
139
140        if (i == 0 && valueType == napi_string) {
141            napi_get_value_string_utf8(env, argv[i], key, 31, &keyLen);
142        } else if (i == 1 && valueType == napi_string) {
143            napi_get_value_string_utf8(env, argv[i], value, 127, &valueLen);
144            break;
145        } else {
146            NAPI_ASSERT(env, false, "type mismatch");
147        }
148    }
149
150    // 获取数据的业务逻辑,这里简单地从一个全局变量中获取
151    auto itr = gKeyValueStorage.find(key);
152    napi_value result = nullptr;
153    if (itr != gKeyValueStorage.end()) {
154        // 获取到数据后创建一个string类型的JS对象
155        napi_create_string_utf8(env, itr->second.c_str(), itr->second.length(), &result);
156    } else if (valueLen > 0) {
157        // 没有获取到数据使用默认值创建JS对象
158        napi_create_string_utf8(env, value, valueLen, &result);
159    } else {
160        NAPI_ASSERT(env, false, "key does not exist");
161    }
162    // 返回结果
163    return result;
164}
165```
166
167#### get 函数实现
168
169如上注册时所写,`get`对应的函数式`JSStorageGet`。
170
171```c
172static napi_value JSStorageGet(napi_env env, napi_callback_info info)
173{
174    GET_PARAMS(env, info, 3);
175    NAPI_ASSERT(env, argc >= 1, "requires 1 parameter");
176
177    // StorageAsyncContext是自己定义的一个类,用于保存执行过程中的数据
178    StorageAsyncContext* asyncContext = new StorageAsyncContext();
179
180    asyncContext->env = env;
181
182    // 获取参数
183    for (size_t i = 0; i < argc; i++) {
184        napi_valuetype valueType;
185        napi_typeof(env, argv[i], &valueType);
186
187        if (i == 0 && valueType == napi_string) {
188            napi_get_value_string_utf8(env, argv[i], asyncContext->key, 31, &asyncContext->keyLen);
189        } else if (i == 1 && valueType == napi_string) {
190            napi_get_value_string_utf8(env, argv[i], asyncContext->value, 127, &asyncContext->valueLen);
191        } else if (i == 1 && valueType == napi_function) {
192            napi_create_reference(env, argv[i], 1, &asyncContext->callbackRef);
193            break;
194        } else if (i == 2 && valueType == napi_function) {
195            napi_create_reference(env, argv[i], 1, &asyncContext->callbackRef);
196        } else {
197            NAPI_ASSERT(env, false, "type mismatch");
198        }
199    }
200
201    napi_value result = nullptr;
202
203    // 根据参数判断开发者使用的是promise还是callback
204    if (asyncContext->callbackRef == nullptr) {
205        // 创建promise
206        napi_create_promise(env, &asyncContext->deferred, &result);
207    } else {
208        napi_get_undefined(env, &result);
209    }
210
211    napi_value resource = nullptr;
212    napi_create_string_utf8(env, "JSStorageGet", NAPI_AUTO_LENGTH, &resource);
213
214    napi_create_async_work(
215        env, nullptr, resource,
216        // 回调1:此回调由napi异步执行,里面就是需要异步执行的业务逻辑。由于是异步线程执行,所以不要在此通过napi接口操作JS对象。
217        [](napi_env env, void* data) {
218            StorageAsyncContext* asyncContext = (StorageAsyncContext*)data;
219            auto itr = gKeyValueStorage.find(asyncContext->key);
220            if (itr != gKeyValueStorage.end()) {
221                strncpy_s(asyncContext->value, 127, itr->second.c_str(), itr->second.length());
222                asyncContext->status = 0;
223            } else {
224                asyncContext->status = 1;
225            }
226        },
227        // 回调2:此回调在上述异步回调执行完后执行,此时回到了JS线程来回调开发者传入的回调
228        [](napi_env env, napi_status status, void* data) {
229            StorageAsyncContext* asyncContext = (StorageAsyncContext*)data;
230            napi_value result[2] = {0};
231            if (!asyncContext->status) {
232                napi_get_undefined(env, &result[0]);
233                napi_create_string_utf8(env, asyncContext->value, strlen(asyncContext->value), &result[1]);
234            } else {
235                napi_value message = nullptr;
236                napi_create_string_utf8(env, "key does not exist", NAPI_AUTO_LENGTH, &message);
237                napi_create_error(env, nullptr, message, &result[0]);
238                napi_get_undefined(env, &result[1]);
239            }
240            if (asyncContext->deferred) {
241                // 如果走的是promise,那么判断回调1的结果
242                if (!asyncContext->status) {
243                    // 回调1执行成功(status为1)时触发,也就是触发promise里then里面的回调
244                    napi_resolve_deferred(env, asyncContext->deferred, result[1]);
245                } else {
246                    // 回调1执行失败(status为0)时触发,也就是触发promise里catch里面的回调
247                    napi_reject_deferred(env, asyncContext->deferred, result[0]);
248                }
249            } else {
250                // 如果走的是callback,则通过napi_call_function调用callback回调返回结果
251                napi_value callback = nullptr;
252                napi_value returnVal;
253                napi_get_reference_value(env, asyncContext->callbackRef, &callback);
254                napi_call_function(env, nullptr, callback, 2, result, &returnVal);
255                napi_delete_reference(env, asyncContext->callbackRef);
256            }
257            napi_delete_async_work(env, asyncContext->work);
258            delete asyncContext;
259        },
260        (void*)asyncContext, &asyncContext->work);
261    napi_queue_async_work(env, asyncContext->work);
262
263    return result;
264}
265```
266
267### JS Sample Code
268
269```js
270import storage from 'libstorage.so';
271
272export default {
273  testGetSync() {
274  	const name = storage.getSync('name');
275    console.log('name is ' + name);
276  },
277  testGet() {
278    storage.get('name')
279    .then(date => {
280    	console.log('name is ' + data);
281    })
282    .catch(error => {
283    	console.log('error: ' + error);
284    });
285  }
286}
287```
288
289
290
291## 示例二,NetServer 模块——native与js对象绑定
292
293### 模块简介
294
295本例展示了`on/off/once`订阅方法的实现,同时也包含了 C++ 与 JS 对象通过 wrap 接口的绑定。NetServer 模块实现了一个网络服务。
296
297### 接口声明
298
299```typescript
300export class NetServer {
301  function start(port: number): void;
302  function stop(): void;
303  function on('start' | 'stop', callback: Function): void;
304  function once('start' | 'stop', callback: Function): void;
305  function off('start' | 'stop', callback: Function): void;
306}
307```
308
309### 具体实现
310
311完整代码参见:`sample/native_module_netserver/`
312
313#### 模块注册
314
315```c
316static napi_value NetServer::Export(napi_env env, napi_value exports)
317{
318    const char className[] = "NetServer";
319    napi_property_descriptor properties[] = {
320        DECLARE_NAPI_FUNCTION("start", JS_Start),
321        DECLARE_NAPI_FUNCTION("stop", JS_Stop),
322        DECLARE_NAPI_FUNCTION("on", JS_On),
323        DECLARE_NAPI_FUNCTION("once", JS_Once),
324        DECLARE_NAPI_FUNCTION("off", JS_Off),
325    };
326    napi_value netServerClass = nullptr;
327
328    napi_define_class(env, className, sizeof(className), JS_Constructor, nullptr, countof(properties), properties,
329                      &netServerClass);
330
331    napi_set_named_property(env, exports, "NetServer", netServerClass);
332
333    return exports;
334}
335```
336
337#### 在构造函数中绑定 C++ 与 JS 对象
338
339```c
340napi_value NetServer::JS_Constructor(napi_env env, napi_callback_info cbinfo)
341{
342    napi_value thisVar = nullptr;
343    void* data = nullptr;
344    napi_get_cb_info(env, cbinfo, nullptr, nullptr, &thisVar, &data);
345
346    // C++ Native对象,准备与JS对象映射在一起
347    NetServer* netServer = new NetServer(env, thisVar);
348
349    // 使用napi_wrap将netServer与thisVar(即当前创建的这个JS对象)做绑定
350    napi_wrap(
351        env, thisVar, netServer,
352        // JS对象由引擎自动回收释放,当JS对象释放时触发该回调,在改回调中释放netServer
353        [](napi_env env, void* data, void* hint) {
354            printf("NetServer::Destructor\n");
355            NetServer* netServer = (NetServer*)data;
356            delete netServer;
357        },
358        nullptr, nullptr);
359
360    return thisVar;
361}
362```
363
364#### 从 JS 对象中取出 C++ 对象
365
366```c
367napi_value NetServer::JS_Start(napi_env env, napi_callback_info cbinfo)
368{
369    size_t argc = 1;
370    napi_value argv[1] = {0};
371    napi_value thisVar = nullptr;
372    void* data = nullptr;
373    napi_get_cb_info(env, cbinfo, &argc, argv, &thisVar, &data);
374
375    NetServer* netServer = nullptr;
376    // 通过napi_unwrap从thisVar中取出C++对象
377    napi_unwrap(env, thisVar, (void**)&netServer);
378
379    NAPI_ASSERT(env, argc >= 1, "requires 1 parameter");
380
381    napi_valuetype valueType;
382    napi_typeof(env, argv[0], &valueType);
383    NAPI_ASSERT(env, valueType == napi_number, "type mismatch for parameter 1");
384
385    int32_t port = 0;
386    napi_get_value_int32(env, argv[0], &port);
387
388    // 开启服务
389    netServer->Start(port);
390
391    napi_value result = nullptr;
392    napi_get_undefined(env, &result);
393    return result;
394}
395```
396
397`netServer->Start`后回调通过`on`注册的`start`事件。
398
399```c
400int NetServer::Start(int port)
401{
402    printf("NetServer::Start thread_id: %ld \n", uv_thread_self());
403
404    struct sockaddr_in addr;
405    int r;
406
407    uv_ip4_addr("0.0.0.0", port, &addr);
408
409    r = uv_tcp_init(loop_, &tcpServer_);
410    if (r) {
411        fprintf(stderr, "Socket creation error\n");
412        return 1;
413    }
414
415    r = uv_tcp_bind(&tcpServer_, (const struct sockaddr*)&addr, 0);
416    if (r) {
417        fprintf(stderr, "Bind error\n");
418        return 1;
419    }
420
421    r = uv_listen((uv_stream_t*)&tcpServer_, SOMAXCONN, OnConnection);
422    if (r) {
423        fprintf(stderr, "Listen error %s\n", uv_err_name(r));
424        return 1;
425    }
426
427    // 服务启动后触发“start”事件
428    Emit("start", nullptr);
429
430    return 0;
431}
432```
433
434#### 注册或释放(on/off/once)事件,以 on 为例
435
436```c
437napi_value NetServer::JS_On(napi_env env, napi_callback_info cbinfo)
438{
439    size_t argc = 2;
440    napi_value argv[2] = {0};
441    napi_value thisVar = 0;
442    void* data = nullptr;
443    napi_get_cb_info(env, cbinfo, &argc, argv, &thisVar, &data);
444
445    NetServer* netServer = nullptr;
446    // 通过napi_unwrap取出NetServer指针
447    napi_unwrap(env, thisVar, (void**)&netServer);
448
449    NAPI_ASSERT(env, argc >= 2, "requires 2 parameter");
450
451    // 参数类型校验
452    napi_valuetype eventValueType;
453    napi_typeof(env, argv[0], &eventValueType);
454    NAPI_ASSERT(env, eventValueType == napi_string, "type mismatch for parameter 1");
455
456    napi_valuetype eventHandleType;
457    napi_typeof(env, argv[1], &eventHandleType);
458    NAPI_ASSERT(env, eventHandleType == napi_function, "type mismatch for parameter 2");
459
460    char type[64] = {0};
461    size_t typeLen = 0;
462
463    napi_get_value_string_utf8(env, argv[0], type, 63, &typeLen);
464
465    // 注册事件handler
466    netServer->On((const char*)type, argv[1]);
467
468    napi_value result = nullptr;
469    napi_get_undefined(env, &result);
470    return result;
471}
472```
473
474### JS Sample Code
475
476```javascript
477import { NetServer } from 'libnetserver.so';
478
479export default {
480  testNetServer() {
481  	var netServer = new NetServer();
482  	netServer.on('start', (event) => {});
483  	netServer.start(1000); // 端口号1000, start完成后回调上面注册的 “start” 回调
484  }
485}
486```
487
488
489
490## 示例三,在非JS线程中回调JS接口
491
492### 模块简介
493
494本模块介绍如何在非JS线程中回调JS应用的回调函数。例如JS应用中注册了某个sensor的监听,这个sensor的数据是由一个SA服务来上报的,当SA通过IPC调到客户端时,此时的执行线程是一个IPC通信线程,与应用的JS线程是两个不同的线程。这时就需要将执行JS回调的任务抛到JS线程中才能执行,否则会出现崩溃。
495
496### 具体实现
497
498完整代码参见:`sample/native_module_callback/`
499
500#### 模块注册
501
502如下,注册了1个接口`test`,会传入一个参数,类型为包含一个参数的函数。
503
504```c++
505/***********************************************
506 * Module export and register
507 ***********************************************/
508static napi_value CallbackExport(napi_env env, napi_value exports)
509{
510    static napi_property_descriptor desc[] = {
511        DECLARE_NAPI_FUNCTION("test", JSTest)
512    };
513    NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc));
514    return exports;
515}
516
517// callback module define
518static napi_module callbackModule = {
519    .nm_version = 1,
520    .nm_flags = 0,
521    .nm_filename = nullptr,
522    .nm_register_func = CallbackExport,
523    .nm_modname = "callback",
524    .nm_priv = ((void*)0),
525    .reserved = { 0 },
526};
527
528// callback module register
529extern "C" __attribute__((constructor)) void CallbackTestRegister()
530{
531    napi_module_register(&callbackModule);
532}
533```
534
535#### 获取env中的loop,抛任务回JS线程
536
537```c++
538#include <thread>
539
540#include "napi/native_api.h"
541#include "napi/native_node_api.h"
542
543#include "uv.h"
544
545struct CallbackContext {
546    napi_env env = nullptr;
547    napi_ref callbackRef = nullptr;
548    int retData = 0;
549};
550
551void callbackTest(CallbackContext* context)
552{
553    uv_loop_s* loop = nullptr;
554    // 此处的env需要在注册JS回调时保存下来。从env中获取对应的JS线程的loop。
555    napi_get_uv_event_loop(context->env, &loop);
556
557    // 创建uv_work_t用于传递私有数据,注意回调完成后需要释放内存,此处省略生成回传数据的逻辑,传回int类型1。
558    uv_work_t* work = new uv_work_t;
559    context->retData = 1;
560    work->data = (void*)context;
561
562    // 调用libuv接口抛JS任务到loop中执行。
563    uv_queue_work(
564        loop,
565        work,
566        // 此回调在另一个普通线程中执行,用于处理异步任务,回调执行完后执行下面的回调。本场景下该回调不需要执行任务。
567        [](uv_work_t* work) {},
568        // 此回调会在env对应的JS线程中执行。
569        [](uv_work_t* work, int status) {
570            CallbackContext* context = (CallbackContext*)work->data;
571            napi_handle_scope scope = nullptr;
572            // 打开handle scope用于管理napi_value的生命周期,否则会内存泄露。
573            napi_open_handle_scope(context->env, &scope);
574            if (scope == nullptr) {
575                return;
576            }
577
578            // 调用napi。
579            napi_value callback = nullptr;
580            napi_get_reference_value(context->env, context->callbackRef, &callback);
581            napi_value retArg;
582            napi_create_int32(context->env, context->retData, &retArg);
583            napi_value ret;
584            napi_call_function(context->env, nullptr, callback, 1, &retArg, &ret);
585            napi_delete_reference(context->env, context->callbackRef);
586
587            // 关闭handle scope释放napi_value。
588            napi_close_handle_scope(context->env, scope);
589
590            // 释放work指针。
591            if (work != nullptr) {
592                delete work;
593            }
594
595            delete context;
596        }
597    );
598}
599
600static napi_value JSTest(napi_env env, napi_callback_info info)
601{
602    size_t argc = 1;
603    napi_value argv[1] = { 0 };
604    napi_value thisVar = nullptr;
605    void* data = nullptr;
606    napi_get_cb_info(env, info, &argc, argv, &thisVar, &data);
607
608    // 获取第一个入参,即需要后续触发的回调函数
609    napi_valuetype valueType = napi_undefined;
610    napi_typeof(env, argv[0], &valueType);
611    if (valueType != napi_function) {
612        return nullptr;
613    }
614    // 存下env与回调函数,用于传递
615    auto asyncContext = new CallbackContext();
616    asyncContext->env = env;
617    napi_create_reference(env, argv[0], 1, &asyncContext->callbackRef);
618    // 模拟抛到非js线程执行逻辑
619    std::thread testThread(callbackTest, asyncContext);
620    testThread.detach();
621
622    return nullptr;
623}
624```
625
626### JS Sample Code
627
628```js
629import callback from 'libcallback.so';
630
631export default {
632  testcallback() {
633  	callback.test((data) => {
634      console.error('test result = ' + data)
635    })
636  }
637}
638```
639