1#### AKI 版 NativeC++ 应用示例 2 3##### AKI简介 4 5AKI (Alpha Kernel Interacting) 是一款边界性编程体验友好的ArkTs FFI开发框架,针对OpenHarmony Native开发提供JS与C/C++跨语言访问场景解决方案。支持极简语法糖使用方式,简洁代码逻辑完成JS与C/C++的无障碍跨语言互调。 6 7简单讲,AKI就是对NAPI进行了一层封装,提供对典型应用场景的包装,减轻了用户开发NAPI层的开发负担,具体优点如下: 8 9* 解耦FFI代码与业务代码,友好的边界性编程体验; 10 11* 提供数据类型转换、函数绑定、对象绑定、线程安全等特性; 12 13* 支持JS & C/C++互调 14* 支持与Node-API即NAPI的嵌套使用 15 16 17 18##### AKI代码样例 19 20* Native C/C++ 业务代码: 21 22```c++ 23#include <string> 24#include <aki/jsbind.h> 25 26std::string SayHello(std::string msg) { return msg + " too."; } 27 28// Step 1 注册 AKI 插件 29JSBIND_ADDON(hello) // 注册 AKI 插件名: 即为编译*.so名称,规则与NAPI一致 30 31// Step 2 注册 FFI 特性 32JSBIND_GLOBAL() { JSBIND_FUNCTION(SayHello); } 33``` 34 35* ArkTS 业务代码: 36 37```typescript 38import aki from 'libentry.so'; 39let msg = aki.SayHello("hell to cpp"); 40``` 41 42参考:https://gitee.com/openharmony-sig/aki 43 44 45 46##### 工程调试 47 48* 使用DevEco Studio Next Release,Build Version: 5.0.3.900, built on October 8, 2024 49 50* 使用5.0.0 Release 镜像,在rk3568上运行测试 51 52* 使用[资源](https://gitee.com/openharmony/napi_generator/releases/download/%E6%B5%8B%E8%AF%95%E7%94%A8%E8%B5%84%E6%BA%90/akitutorial_package.zip)内的文件分别拷贝到对应路径(解压后thirdparty内容拷贝到akitutorials\entry\src\main\cpp\thirdparty下,libs里的内容拷贝到akitutorials\entry\libs下) 53 54* 编译运行 55 56 57 58##### AKI 接口说明 59 60* **binding.h**:提供提供函数的类的注册方法,对应JSBIND_FUNCTION,JSBIND_CLASS 61 62 * c++代码: 63 64 ```c++ 65 #include <string> 66 #include <aki/jsbind.h> 67 68 std::string SayHello(std::string msg) 69 { 70 return msg + " too."; 71 } 72 73 JSBIND_GLOBAL() 74 { 75 JSBIND_FUNCTION(SayHello); 76 } 77 78 JSBIND_ADDON(hello); 79 ``` 80 81 82 83 * js代码: 84 85 ```js 86 import aki from 'libhello.so' // 插件名 87 88 let message = aki.SayHello("hello world"); 89 ``` 90 91 92 93* **jsbind.h**:提供将JS方法绑定至C/C++层使用的能力。使用JSBind类,提供bindFunction,unbindFunction方法,支持JS线程安全函数注册和使用。如:JSBind.bindFunction,aki::JSBind::GetJSFunction,如: 94 95 * c++ 代码: 96 97 ```c++ 98 #include <string> 99 #include <aki/jsbind.h> 100 101 void DoSomething() { 102 // 索引 JS 函数句柄 103 auto jsFunc = aki::JSBind::GetJSFunction("sayHelloFromJS"); 104 105 // Invoke 指定 JS 方法的返回值类型 106 auto result = jsFunc->Invoke<std::string>("hello from C++"); // 可在非JS线程执行 107 // result == "hello from JS" 108 } 109 ``` 110 111 * js 代码: 112 113 ```js 114 import libAddon from 'libhello.so' // 插件名 115 116 function sayHelloFromJS (value) { 117 console.log('what do you say: ' + value); 118 return "hello from JS" 119 } 120 121 libAddon.JSBind.bindFunction("sayHelloFromJS", sayHelloFromJS); 122 ``` 123 124 125 126* **version.h**: 提供aki版本号,如: 127 128 * c++ 代码: 129 130 ```c++ 131 std::string version(aki::Version::GetVersion()); 132 ``` 133 134 135 136* **value.h**:提供对value类型的转换,即提供通用类型转换(类似napi_value),Value支持string,number,array等类型,也可通过globalThis拿到对应js句柄,在c++测执行对应方法,如: 137 138 * c++ 代码: 139 140 ```c++ 141 aki::Value FromGlobalJSONStringify(aki::Value obj) { 142 // 获取js引入的JSON库 143 aki::Value json = aki::Value::FromGlobal("JSON"); 144 // 执行JSON.stringify方法将obj输出为json_string 145 return json["stringify"](obj); 146 } 147 ``` 148 149 * js 代码: 150 151 ```js 152 let stringify: string = aki.FromGlobalJSONStringify({ 153 'name': 'aki', 154 'age': 1}); 155 ``` 156 157 以上是展示在C++侧获取默认命名空间的方法,还可以自定义,如: 158 159 * c++ 代码: 160 161 ```c++ 162 std::string SayHello(std::string msg) { 163 std::string version(aki::Version::GetVersion()); 164 aki::Value buf = aki::Value::FromGlobal("buffer"); 165 aki::Value bufObj = buf["alloc"](10, "a"); 166 aki::Value isBuffer = buf["isBuffer"](bufObj); 167 bool isBuf = isBuffer.As<bool>(); 168 std::string res = isBuf ? "true" : "false"; 169 return msg + " too." + version + res; 170 } 171 ``` 172 173 * js 代码:注意,必须是ts文件,如果ets文件,编译报错不支持globalThis 174 175 ```js 176 import buffer from '@ohos.buffer'; 177 178 export class Test { 179 static setBuffer(){ 180 globalThis.buffer = buffer; 181 } 182 } 183 ``` 184 185 186 187* 异步开发: 188 189 * cpp 代码: 190 191 ```c++ 192 static aki::Promise ReturnPromiseResolveLater() 193 { 194 aki::Promise promise; 195 196 std::thread t([promise] () { 197 aki::TaskRunner::PostTask("main", [promise] () { 198 promise.Resolve(1); 199 }); 200 }); 201 t.detach(); 202 return promise; 203 } 204 ``` 205 206 * js 代码: 207 208 ```js 209 libPromise.JSBind.initTaskRunner("main"); 210 libPromise.ReturnPromiseResolveLater().then((value) => { 211 console.log('[AKI] ReturnPromiseResolveLater then: ' + value); 212 }) 213 ``` 214 215 216 217 * 218 219* 混合开发:即用aki也用napi 220 221 * cpp 代码: 222 223 ```c++ 224 #include "napi/native_api.h" 225 #include <aki/jsbind.h> 226 227 static napi_value addByNAPI(napi_env env, napi_callback_info info) { 228 ...... 229 return sum; 230 } 231 232 EXTERN_C_START 233 static napi_value Init(napi_env env, napi_value exports) { 234 napi_property_descriptor desc[] = { 235 {"addByNAPI", nullptr, addByNAPI, nullptr, nullptr, nullptr, napi_default, nullptr}}; 236 napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc); 237 238 exports = aki::JSBind::BindSymbols(env, exports); // aki::BindSymbols 函数传入 js 对象绑定符号 239 return exports; 240 } 241 EXTERN_C_END 242 243 // napi 方式的native绑定 244 static napi_module demoModule = { 245 .nm_version = 1, 246 .nm_flags = 0, 247 .nm_filename = nullptr, 248 .nm_register_func = Init, 249 .nm_modname = "entry", 250 .nm_priv = ((void *)0), 251 .reserved = {0}, 252 }; 253 254 extern "C" __attribute__((constructor)) void RegisterHelloModule(void) { napi_module_register(&demoModule); } 255 256 // aki 方式的native绑定 257 std::string SayHello(std::string msg) { 258 return msg + " too."; 259 } 260 261 // Step 1 注册 AKI 插件 262 JSBIND_ADDON(hello) // 注册 AKI 插件名: 即为编译*.so名称,规则与NAPI一致 263 264 // Step 2 注册 FFI 特性 265 JSBIND_GLOBAL() { 266 JSBIND_FUNCTION(SayHello); 267 } 268 ``` 269 270 271 272 * js 代码: 273 274 ```js 275 import aki from 'libentry.so'; 276 277 let msg: string = aki.SayHello("hell to cpp"); 278 hilog.info(0x0000, 'testTag', 'Test SayHello = %{public}s', msg); 279 let res: number = aki.addByNAPI(2, 3); 280 hilog.info(0x0000, 'testTag', 'Test NAPI 2 + 3 = %{public}d', res); 281 ``` 282 283 284 285 286 287##### 实现原理: 288 289aki 还是利用node-api技术提供 js 和 cpp 间跨语言的交互接口,主要用于开发针对ArkTS的c/c++插件,帮助开发者在ArkTS(ts)中调用本地代码,c/c++库,同时保证跨版本兼容性; 290 291##### N-API 主要特点 292 2931. **跨版本兼容性**:N-API 提供了一个稳定的 ABI(应用程序二进制接口),这意味着扩展可以在不同版本的 Node.js 上运行,而无需重新编译或修改代码。 2942. **简化开发**:N-API 抽象了一些底层的细节,使得开发者可以专注于应用逻辑,而不必担心 Node.js 内部的实现。 2953. **性能优化**:通过使用本地代码,N-API 可以提高性能,特别是在需要进行大量计算或处理复杂数据结构的情况下。 2964. **安全性**:N-API 提供了一些安全机制,帮助开发者预防常见的内存管理问题,如缓冲区溢出等。 297 298##### 使用场景 299 300- **性能敏感的应用**:例如,大量数据处理、图像处理、加密和解密等。 301- **需要访问底层系统功能**:如文件系统、网络协议等。 302- **重用已有的 C/C++ 库**:如果有成熟的 C/C++ 库,可以通过 N-API 将其封装成 Node.js 模块进行使用。 303 304 305 3061. 依赖库:[CMake参考](https://gitee.com/wshikh/aki/blob/master/src/CMakeLists.txt) 307 308```cmake 309target_link_libraries(${TARGET_NAME} PUBLIC libace_napi.z.so libhilog_ndk.z.so uv) 310``` 311 3122. 编译配置:[CMake参考](https://gitee.com/wshikh/aki/blob/master/src/CMakeLists.txt) 313 314```cmake 315//CMakeLists.txt 316option(AKI_BUILDING_SHARED "compile for shared library" ON) 317option(AKI_ENABLE_NAPI "using node-api" ON) 318option(AKI_ENABLE_INSTALL_OHOS "" OFF) 319option(AKI_ENABLE_DECLARATION "" OFF) 320option(AKI_ENABLE_TRACING "DO NOT USE THIS option !!!" OFF) 321option(AKI_ENABLE_CXX_STANDARD_11 "" OFF) 322``` 323 3243. napi 注册 325 326* N-API 注册方法:声明模块,利用 napi 接口进行注册 327 328```cpp 329// 初始化导出模块的属性描述符 330EXTERN_C_START 331static napi_value Init(napi_env env, napi_value exports) 332{ 333 napi_property_descriptor desc[] = { 334 { "add", nullptr, Add, nullptr, nullptr, nullptr, napi_default, nullptr } 335 }; 336 napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc); 337 return exports; 338} 339EXTERN_C_END 340 341static napi_module demoModule = { 342 .nm_version = 1, 343 .nm_flags = 0, 344 .nm_filename = nullptr, 345 .nm_register_func = Init, 346 .nm_modname = "entry", 347 .nm_priv = ((void*)0), 348 .reserved = { 0 }, 349}; 350 351// 注册模块(也可以称之为 c/c++插件) 352extern "C" __attribute__((constructor)) void RegisterEntryModule(void) 353{ 354 napi_module_register(&demoModule); 355} 356``` 357 358* aki 注册方法: 359 360```cpp 361// Step 1 注册 AKI 插件 362JSBIND_ADDON(hello) // 注册 AKI 插件名: 即为编译*.so名称,规则与NAPI一致 363 364// Step 2 注册 FFI 特性 365JSBIND_GLOBAL() { 366 JSBIND_FUNCTION(SayHello); 367 JSBIND_FUNCTION(FromGlobalJSONStringify); 368} 369``` 370 371* JSBIND_ADDON: 372 373```c 374#define JSBIND_ADDON_LAZY(addonName) \ 375EXTERN_C_START \ 376static napi_module _module = { \ 377 .nm_version =1, \ 378 .nm_flags = 0, \ 379 .nm_filename = nullptr, \ 380 .nm_register_func = aki::JSBind::BindSymbols, \ 381 .nm_modname = #addonName, \ 382 .nm_priv = ((void*)0), \ 383 .reserved = { 0 }, \ 384}; \ 385extern "C" __attribute__((constructor)) void Register##addonName(void) { \ 386 napi_module_register(&_module); \ 387 AKI_LOG(INFO) << "register AKI addon: " << #addonName; \ 388} \ 389EXTERN_C_END 390 391#define JSBIND_ADDON(addonName) \ 392JSBIND_ADDON_LAZY(addonName) 393``` 394 395 396 397* JSBIND_GLOBAL: 398 399```c 400// 宏定义,JSBIND_GLOBAL 转 namespace 401#define JSBIND_GLOBAL() namespace 402 403// 宏定义,JSBIND_FUNCTION 转 aki::FunctionDefiner,变量名就是definer+__LINE__,后面执行的是FunctionDefiner构造函数 404#define JSBIND_FUNCTION(__name, ...) aki::FunctionDefiner JSBIND_UNIQUE(definer, __LINE__)(aki::AliasName(#__name, ##__VA_ARGS__), &__name) 405 406// 宏定义,JSBIND_FUNCTION 转 aki::PFunctionDefiner,变量名就是definer+__LINE__,后面执行的是PFunctionDefiner构造函数 407#define JSBIND_PFUNCTION(__name, ...) aki::PFunctionDefiner JSBIND_UNIQUE(definer, __LINE__)(aki::AliasName(#__name, ##__VA_ARGS__), &__name) 408``` 409 410 411 412* FunctionDefiner & Init: 413 414```c++ 415namespace aki { 416class FunctionDefiner { 417public: 418 ...... 419 // 注册方法 420 Binding::RegisterFunction(name, Bind#er::AddInvoker(func), &Bind#er::GetInstance()); 421}; 422} 423``` 424 425 426 427* BindSymbols 428 429```c++ 430// 对应的就是导出模块里的注册方法: .nm_register_func = aki::JSBind::BindSymbols, 431napi_value aki::JSBind::BindSymbols(napi_env env, napi_value exports) 432{ 433 return Init(env, exports); 434} 435 436EXTERN_C_START 437static napi_value Init(napi_env env, napi_value exports) { 438 ...... 439 for (auto& function : aki::Binding::GetFunctionList()) { 440 auto akibinder = function.GetBinder(); 441 auto wrapper = reinterpret_cast<NapiWrapperFunctionInfo>(akibinder->GetWrapper()); 442 443 napi_status status; 444 aki::BindInfo* info = new aki::BindInfo(); 445 info->functionNumber = function.GetInvokerId(); 446 // 定义function描述符 447 napi_property_descriptor desc = DECLARE_NAPI_FUNCTION(function.GetName(), wrapper, info); 448 // 在导出对象里增加方法属性 449 status = napi_define_properties(env, exports, 1, &desc); 450 AKI_DCHECK(status == napi_ok) << "napi_define_properties failed when binding global function: " << function.GetName(); 451 AKI_DLOG(DEBUG) << "binding global function: " << function.GetName(); 452 } 453 454 // 下面还有对 Enumeration 和 Class 的注册 455 for (auto& enumeration : aki::Binding::GetEnumerationList()) { 456 ...... 457 } 458 459 for (auto& xlass : aki::Binding::GetClassList()) { 460 ...... 461 } 462 463} 464``` 465 466 467 468* 默认注册类 JSBind:提供native直接调用js函数的方法和执行js的线程任务,其特点有: 469 470 * 线程安全:可在非JS线程直接调用。最终会由框架调度JS线程执行业务; 471 * 阻塞式调用:在非JS线程时存在跨线程任务调度。 C++ 会等待 JavaScript 函数执行结束后返回; 472 473 ```c++ 474 namespace aki { 475 class AKI_EXPORT JSBind { 476 public: 477 #if JSBIND_USING_NAPI 478 static napi_value BindSymbols(napi_env env, napi_value exports); 479 static napi_value BindSymbols(napi_env env, napi_value exports, std::string moduleName); 480 static napi_value BindSymbols(const char* module); 481 static void SetScopedEnv(napi_env env); 482 static napi_env GetScopedEnv(); 483 #endif // JSBIND_USING_NAPI 484 485 static int bindFunction(const std::string& name, JSFunction func); 486 static int unbindFunction(const std::string& name); 487 static void InitTaskRunner(const std::string& name); 488 489 #if JSBIND_SUPPORT_DECLARATION 490 static void Reflect(aki::Callback<void (intptr_t, int32_t)> outputBuildInType, 491 aki::Callback<void (std::string, std::vector<intptr_t>)> outputFunction); 492 static void QueryType(intptr_t typeId, 493 aki::Callback<void (int32_t, std::vector<intptr_t>)> outputType); 494 #endif 495 static const JSFunction* GetJSFunction(const std::string& name); 496 private: 497 }; 498 499 // | static | 500 int JSBind::bindFunction(const std::string& name, JSFunction func) 501 { 502 return Binding::RegisterJSFunction(name, std::make_unique<JSFunction>(std::move(func))); 503 } 504 505 int JSBind::unbindFunction(const std::string& name) 506 { 507 return Binding::UnRegisterJSFunction(name); 508 } 509 510 void aki::JSBind::InitTaskRunner(const std::string& name) { 511 aki::TaskRunner::Create(name); 512 } 513 } 514 using namespace aki; 515 JSBIND_CLASS(JSBind) { 516 JSBIND_METHOD(bindFunction); 517 JSBIND_METHOD(unbindFunction); 518 JSBIND_METHOD(InitTaskRunner, "initTaskRunner"); 519 520 #if JSBIND_SUPPORT_DECLARATION 521 JSBIND_METHOD(Reflect, "reflect"); 522 JSBIND_METHOD(QueryType, "queryType"); 523 #endif 524 } 525 ``` 526 527 528 529 530 531 532### 总结 533 5341. 最新5.0.0 Release 的IDE也能进行开发NativeC++项目,不过要修改工程,参考这个修改:https://forums.openharmony.cn/forum.php?mod=viewthread&tid=3550&page=1#pid8694 ; 5352. AKI 是一个native应用开发的快速框架,提供了绑定函数,类,枚举给js层使用,以及从native侧获取js全局对象,js方法,js异步任务的方法;给应用开发者提供跨语言的互相访问能力; 5363. AKI 可以和原来的 napi 开发方式并存,混合使用; 537 538 539 540 541 542