README.md
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