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)