• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Using Native APIs in Application Projects
2
3OpenHarmony applications use JavaScript (JS) when calling native APIs. The native APIs (NAPIs) provided by the [ace_napi](https://gitee.com/openharmony/arkui_napi/tree/master) repository are used to implement interaction with JS. The names of the NAPIs are the same as those in the third-party **Node.js**. For details about the interfaces supported, see **libnapi.ndk.json** in the ace_napi repository.
4
5## How to Develop
6
7The DevEco Studio has a default project that uses NAPIs. You can choose **File** > **New** > **Create Project** to create a **Native C++** project. The **cpp** directory is generated in the **main** directory. You can use the NAPIs provided by the **ace_napi** repository for development.
8
9You can import the native .so that contains the JS processing logic. For example, **import hello from 'libhello.so'** to use the **libhello.so** capability. Then, the JS object created using the NAPI can be passed to the **hello** object of the application to call the native capability.
10
11## Development Guidelines
12
13### Registration
14
15* Add **static** to the **nm_register_func** function to prevent symbol conflicts with other .so files.
16* The name of the module registration entry, that is, the function decorated by **\_\_attribute\_\_((constructor))**, must be unique.
17
18### .so Naming Rules
19
20The .so file names must comply with the following rules:
21
22* Each module has a .so file.
23* The **nm_modname** field in **napi_module** must be the same as the module name. For example, if the module name is **hello**, name the .so file **libhello.so**. The sample code for importing the .so file is **import hello from 'libhello.so'**.
24
25### JS Objects and Threads
26
27The Ark engine prevents NAPIs from being called to operate JS objects in non-JS threads. Otherwise, the application will crash. Observe the following rules:
28
29* The NAPIs can be used only in JS threads.
30* **env** is bound to a thread and cannot be used across threads. The JS object created by a NAPI can be used only in the thread, in which the object is created, that is, the JS object is bound to the **env** of the thread.
31
32### Importing Header Files
33
34Before using NAPI objects and methods, include **napi/native_api.h**. Otherwise, if only the third-party library header file is included, an error will be reporting, indicating that the interface cannot be found.
35
36### napi_create_async_work
37
38**napi_create_async_work** has two callbacks:
39
40* **execute**: processes service logic asynchronously. This callback is not executed by a JS thread; therefore, it cannot call any NAPI. The return value of **execute** is processed by the **complete** callback.
41
42* **complete**: calls the NAPI to encapsulate the return value of **execute** into a JS object and return it for processing. This callback is executed by a JS thread.
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## Encapsulating Synchronous and Asynchronous APIs for the Storage Module
57
58### Overview
59
60This example shows how to encapsulate the synchronous and asynchronous APIs of the **Storage** module. The **Storage** module implements the functions of storing, obtaining, deleting, and clearing data.
61
62### API Declaration
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### Implementation
84
85You can obtain the complete code from sample/native_module_storage/ in the [OpenHarmony/arkui_napi](https://gitee.com/openharmony/arkui_napi/tree/master) repository.
86
87**1. Register the module.**
88
89Register four synchronous APIs (**getSync**, **setSync**, **removeSync**, and **clearSync**) and four asynchronous APIs (**get**, **set**, **remove**, and **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// Register the storage module
122extern "C" __attribute__((constructor)) void StorageRegister()
123{
124    napi_module_register(&storage_module);
125}
126```
127
128**2. Implement getSync.**
129
130The **getSync** function registered for the **Storage** module is **JSStorageGetSync**. Obtain data from **gKeyValueStorage**, create a string object, and return the object created.
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    // Parse parameters.
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    // Service logic for obtaining data. This example simply obtains data from a global variable.
158    auto itr = gKeyValueStorage.find(key);
159    napi_value result = nullptr;
160    if (itr != gKeyValueStorage.end()) {
161        // Use the data obtained to create a JS object of the string type.
162        napi_create_string_utf8(env, itr->second.c_str(), itr->second.length(), &result);
163    } else if (valueLen > 0) {
164        // If no data is obtained, use the default value to create a JS object.
165        napi_create_string_utf8(env, value, valueLen, &result);
166    } else {
167        NAPI_ASSERT(env, false, "key does not exist");
168    }
169    // Return the result.
170    return result;
171}
172```
173
174**3. Implement get().**
175
176The **get** function registered for the **Storage** module is **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 is a custom class used to store data during execution.
185    StorageAsyncContext* asyncContext = new StorageAsyncContext();
186
187    asyncContext->env = env;
188
189    // Obtain parameters.
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    // Determine whether promise or callback is used based on the parameters.
211    if (asyncContext->callbackRef == nullptr) {
212        // Create a 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        // Callback 1: This callback contains the service logic to be asynchronously executed and is asynchronously executed by the NAPI. Do not operate JS objects using the NAPI because the execution is asynchronous.
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        // Callback 2: This callback is invoked after callback 1 is complete. The JS thread invokes the callback passed in.
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                // If a promise is used, check the result of callback 1.
249                if (!asyncContext->status) {
250                    // Triggered when callback 1 is successful (status is 1), that is, to invoke the callback passed in then in the promise.
251                    napi_resolve_deferred(env, asyncContext->deferred, result[1]);
252                } else {
253                    // Triggered when callback 1 fails (status is 0), that is, to invoke the callback passed in catch in the promise.
254                    napi_reject_deferred(env, asyncContext->deferred, result[0]);
255                }
256            } else {
257                // If a callback is used, use napi_call_function to invoke the callback to return the result.
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**JS Sample Code**
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## Binding Native and JS Objects for the NetServer Module
299
300### Overview
301
302This example shows how to implement the **on**, **off**, and **once** methods and bind C++ and JS objects using **wrap()**. The **NetServer** module implements the network service.
303
304### API Declaration
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### Implementation
317
318You can obtain the complete code from **sample/native_module_netserver/** in the [OpenHarmony/arkui_napi](https://gitee.com/openharmony/arkui_napi/tree/master) repository.
319
320**1. Register the module.**
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. Bind C++ and JS objects in a constructor.**
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 object to be mapped to the JS object.
354    NetServer* netServer = new NetServer(env, thisVar);
355
356    // Use napi_wrap to bind netServer and thisVar (JS object created).
357    napi_wrap(
358        env, thisVar, netServer,
359        // The JS object is automatically released by the engine. When the JS object is released, the callback is triggered to release 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. Obtain a C++ object from a JS object.**
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    // Obtain the C++ object from thisVar using napi_unwrap.
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    // Start the netServer service.
396    netServer->Start(port);
397
398    napi_value result = nullptr;
399    napi_get_undefined(env, &result);
400    return result;
401}
402```
403
404After **netServer->Start** is executed, call back the **start** event registered by **on()**.
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    // Trigger the start event after the service is started.
435    Emit("start", nullptr);
436
437    return 0;
438}
439```
440
441**4. Call on() to register an event observer.**
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    // Obtain the NetServer pointer using napi_unwrap.
454    napi_unwrap(env, thisVar, (void**)&netServer);
455
456    NAPI_ASSERT(env, argc >= 2, "requires 2 parameter");
457
458    // Verify the parameter type.
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    // Register the event 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**JS Sample Code**
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); // The port number is 1000. After start is executed, invoke the start callback registered.
491  }
492}
493```
494
495
496
497## Calling Back a JS API in a Non-JS Thread
498
499### Overview
500
501This example describes how to invoke a JS callback in a non-JS thread. For example, a sensor listener is registered for a JS application. The sensor data is reported by an SA. When the SA invokes the client through Inter-Process Communication (IPC), the execution thread is an IPC thread, which is different from the JS thread of the SA. In this case, the JS callback must be thrown to the JS thread to execute. Otherwise, the application will crash.
502
503### Implementation
504
505You can obtain the complete code from **sample/native_module_callback/** in the [OpenHarmony/arkui_napi](https://gitee.com/openharmony/arkui_napi/tree/master) repository.
506
507**1. Register the module.**
508
509Register the **test** API to pass in a parameter.
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// Define the callback.
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// Register the callback.
536extern "C" __attribute__((constructor)) void CallbackTestRegister()
537{
538    napi_module_register(&callbackModule);
539}
540```
541
542**2. Obtain the loop in env and throw the task to a JS thread.**
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    // Save the env when the JS callback is registered. Obtain the loop of the JS thread from env.
562    napi_get_uv_event_loop(context->env, &loop);
563
564    // Create uv_work_t to transfer private data (int type 1 in this example). Note that memory must be released after the callback is complete. The logic for generating the returned data is omitted here.
565    uv_work_t* work = new uv_work_t;
566    context->retData = 1;
567    work->data = (void*)context;
568
569    // Call the libuv API to throw the JS task to the loop for execution.
570    uv_queue_work(
571        loop,
572        work,
573        // This callback is executed in another common thread to process tasks asynchronously. After the callback is complete, execute the next callback. In this scenario, this callback does not need to execute any task.
574        [](uv_work_t* work) {},
575        // This callback is executed in the JS thread bound to env.
576        [](uv_work_t* work, int status) {
577            CallbackContext* context = (CallbackContext*)work->data;
578            napi_handle_scope scope = nullptr;
579            // Open the handle scope to manage the lifecycle of napi_value. Otherwise, memory leakage occurs.
580            napi_open_handle_scope(context->env, &scope);
581            if (scope == nullptr) {
582                return;
583            }
584
585            // Call the NAPIs.
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            // Close the handle scope to release napi_value.
595            napi_close_handle_scope(context->env, scope);
596
597            // Release the work pointer.
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    // Obtain the first input parameter, that is, the callback to be invoked subsequently.
616    napi_valuetype valueType = napi_undefined;
617    napi_typeof(env, argv[0], &valueType);
618    if (valueType != napi_function) {
619        return nullptr;
620    }
621    // Save the env and callback for subsequent transfer.
622    auto asyncContext = new CallbackContext();
623    asyncContext->env = env;
624    napi_create_reference(env, argv[0], 1, &asyncContext->callbackRef);
625    // Simulate the logic for throwing a task to a non-JS thread.
626    std::thread testThread(callbackTest, asyncContext);
627    testThread.detach();
628
629    return nullptr;
630}
631```
632
633**JS Sample Code**
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