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