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