1# 使用JSVM进行class相关开发 2<!--Kit: NDK Development--> 3<!--Subsystem: arkcompiler--> 4<!--Owner: @yuanxiaogou; @string_sz--> 5<!--Designer: @knightaoko--> 6<!--Tester: @test_lzz--> 7<!--Adviser: @fang-jinxu--> 8 9## 简介 10 11使用JSVM-API接口进行class相关开发,处理JavaScript中的类,例如定义类、构造实例等。 12 13## 基本概念 14 15在使用JSVM-API接口进行class相关开发时,需要理解以下基本概念: 16 17- **类**:类是用于创建对象的模板。它提供了一种封装数据和行为的方式,以便于对数据进行处理和操作。类在JavaScript中是建立在原型(prototype)的基础上的,并且还引入了一些类独有的语法和语义。 18- **实例**:实例是通过类创建具体的对象。类定义了对象的结构和行为,而实例则是类的具体表现。通过实例化类,我们可以访问类中定义的属性和方法,并且每个实例都具有自己的属性值。 19 20## 接口说明 21 22| 接口 | 功能说明 | 23| ------------------- | ---------------------------------- | 24| OH_JSVM_NewInstance | 通过给定的构造函数,创建一个实例。| 25| OH_JSVM_GetNewTarget | 获取函数的元属性new.target。| 26| OH_JSVM_DefineClass | 用于在JavaScript中定义一个类,并与对应的C类进行封装和交互。它提供了创建类的构造函数、定义属性和方法的能力,支持C和JavaScript之间的数据交互。| 27| OH_JSVM_Wrap | 在JavaScript对象中封装原生实例。稍后可以使用OH_JSVM_Unwrap()解包原生实例。| 28| OH_JSVM_Unwrap | 解包先前封装在JavaScript对象中的原生实例。| 29| OH_JSVM_RemoveWrap | 解包先前封装在JavaScript对象中的原生实例,并释放封装。| 30|OH_JSVM_DefineClassWithOptions | 定义一个具有给定类名、构造函数、属性和回调处理程序、父类的JavaScript类,并根据传入了DefineClassOptions来决定是否需要为所定义的Class设置属性代理、预留internal-field槽位、为class作为函数进行调用时设置函数回调。| 31 32## 使用示例 33 34JSVM-API接口开发流程参考[使用JSVM-API实现JS与C/C++语言交互开发流程](use-jsvm-process.md),本文仅对接口对应C++相关代码进行展示。 35 36### OH_JSVM_NewInstance 37 38通过给定的构造函数,构建一个实例。 39 40cpp部分代码 41 42```cpp 43// hello.cpp 44#include <string.h> 45#include <fstream> 46 47std::string ToString(JSVM_Env env, JSVM_Value val) { 48 JSVM_Value jsonString = nullptr; 49 JSVM_CALL(OH_JSVM_JsonStringify(env, val, &jsonString)); 50 size_t totalLen = 0; 51 JSVM_CALL(OH_JSVM_GetValueStringUtf8(env, jsonString, nullptr, 0, &totalLen)); 52 size_t needLen = totalLen + 1; 53 char* buff = new char[needLen]; 54 std::memset(buff, 0, needLen); 55 JSVM_CALL(OH_JSVM_GetValueStringUtf8(env, jsonString, buff, needLen, &totalLen)); 56 std::string str(buff); 57 delete[] buff; 58 return str; 59} 60 61// OH_JSVM_NewInstance的样例方法 62static JSVM_Value NewInstance(JSVM_Env env, JSVM_CallbackInfo info) { 63 // 获取js侧传入的两个参数 64 size_t argc = 2; 65 JSVM_Value args[2] = {nullptr}; 66 JSVM_CALL(OH_JSVM_GetCbInfo(env, info, &argc, args, nullptr, nullptr)); 67 JSVM_Value result = nullptr; 68 // 调用OH_JSVM_NewInstance接口,实例化一个对象,将这个对象返回 69 JSVM_CALL(OH_JSVM_NewInstance(env, args[0], 1, &args[1], &result)); 70 std::string str = ToString(env, result); 71 OH_LOG_INFO(LOG_APP, "NewInstance:%{public}s", str.c_str()); 72 return nullptr; 73} 74 75// 通过给定的构造函数,构建一个实例。 76// NewInstance注册回调 77static JSVM_CallbackStruct param[] = { 78 {.data = nullptr, .callback = NewInstance}, 79}; 80 81static JSVM_CallbackStruct *method = param; 82 83// NewInstance方法别名,供JS调用 84static JSVM_PropertyDescriptor descriptor[] = { 85 {"newInstance", nullptr, method++, nullptr, nullptr, nullptr, JSVM_DEFAULT}, 86}; 87``` 88<!-- @[oh_jsvm_newinstance](https://gitcode.com/openharmony/applications_app_samples/blob/master/code/DocsSample/ArkTS/JSVMAPI/JsvmUsageGuide/JsvmAboutClass/newinstance/src/main/cpp/hello.cpp) --> 89 90**样例JS** 91```cpp 92const char *srcCallNative = R"JS( 93 function Fruit(name) { 94 this.name = name; 95 } 96 newInstance(Fruit, "apple"); 97)JS"; 98``` 99**执行结果** 100 101在LOG中输出下面的结果: 102```cpp 103NewInstance:{"name":"apple"} 104``` 105### OH_JSVM_GetNewTarget 106 107用于获取函数的元属性new.target值。在JavaScript中,new.target是一个特殊的元属性,用于检测函数或构造函数是否是通过 'new' 运算符被调用的。 108 109### OH_JSVM_DefineClass 110 111用于在JavaScript中定义一个类,并与对应的C类进行封装和交互。它提供了创建类的构造函数、定义属性和方法的能力,以及在C和JavaScript之间进行数据交互的支持。 112 113cpp部分代码 114 115```cpp 116// hello.cpp 117#include <string> 118 119JSVM_Value CreateInstance(JSVM_Env env, JSVM_CallbackInfo info) { 120 JSVM_Value newTarget; 121 // 获取构造函数的new.target值 122 JSVM_CALL(OH_JSVM_GetNewTarget(env, info, &newTarget)); 123 OH_LOG_INFO(LOG_APP, "Create Instance"); 124 OH_LOG_INFO(LOG_APP, "NAPI MyObject::New %{public}s", newTarget != nullptr ? "newTarget != nullptr" : "newTarget == nullptr"); 125 JSVM_Value jsObject = nullptr; 126 JSVM_CALL(OH_JSVM_CreateObject(env, &jsObject)); 127 JSVM_Value jsName = nullptr; 128 JSVM_CALL(OH_JSVM_CreateStringUtf8(env, "name", JSVM_AUTO_LENGTH, &jsName)); 129 JSVM_Value jsValue = nullptr; 130 JSVM_CALL(OH_JSVM_CreateStringUtf8(env, "lilei", JSVM_AUTO_LENGTH, &jsValue)); 131 JSVM_CALL(OH_JSVM_SetProperty(env, jsObject, jsName, jsValue)); 132 return jsObject; 133} 134 135std::string ToString(JSVM_Env env, JSVM_Value val) { 136 JSVM_Value jsonString = nullptr; 137 JSVM_CALL(OH_JSVM_JsonStringify(env, val, &jsonString)); 138 size_t totalLen = 0; 139 JSVM_CALL(OH_JSVM_GetValueStringUtf8(env, jsonString, nullptr, 0, &totalLen)); 140 size_t needLen = totalLen + 1; 141 char* buff = new char[needLen]; 142 std::memset(buff, 0, needLen); 143 JSVM_CALL(OH_JSVM_GetValueStringUtf8(env, jsonString, buff, needLen, &totalLen)); 144 std::string str(buff); 145 delete[] buff; 146 return str; 147} 148 149// 封装c++中的自定义数据结构 150JSVM_Value DefineClass(JSVM_Env env, JSVM_CallbackInfo info) { 151 JSVM_CallbackStruct param; 152 param.data = nullptr; 153 param.callback = CreateInstance; 154 JSVM_Value cons; 155 // 用于在JavaScript中定义一个类 156 JSVM_CALL(OH_JSVM_DefineClass(env, "MyObject", JSVM_AUTO_LENGTH, ¶m, 0, nullptr, &cons)); 157 JSVM_Value instanceValue = nullptr; 158 // 作为class的构造函数调用 159 JSVM_CALL(OH_JSVM_NewInstance(env, cons, 0, nullptr, &instanceValue)); 160 std::string str = ToString(env, instanceValue); 161 OH_LOG_INFO(LOG_APP, "NewInstance:%{public}s", str.c_str()); 162 163 // 作为普通的函数调用 164 JSVM_Value global = nullptr; 165 JSVM_CALL(OH_JSVM_GetGlobal(env, &global)); 166 JSVM_Value key; 167 JSVM_CALL(OH_JSVM_CreateStringUtf8(env, "Constructor", JSVM_AUTO_LENGTH, &key)); 168 JSVM_CALL(OH_JSVM_SetProperty(env, global, key, cons)); 169 JSVM_Value result = nullptr; 170 JSVM_CALL(OH_JSVM_CallFunction(env, global, cons, 0, nullptr, &result)); 171 std::string buf = ToString(env, result); 172 OH_LOG_INFO(LOG_APP, "NewInstance:%{public}s", buf.c_str()); 173 return nullptr; 174} 175 176// 注册DefineClass的方法 177JSVM_CallbackStruct param[] = { 178 {.data = nullptr, .callback = DefineClass}, 179}; 180 181static JSVM_CallbackStruct *method = param; 182 183// DefineClass方法别名,供JS调用 184static JSVM_PropertyDescriptor descriptor[] = { 185 {"defineClass", nullptr, method++, nullptr, nullptr, nullptr, JSVM_DEFAULT}, 186}; 187 188``` 189<!-- @[oh_jsvm_defineclass](https://gitcode.com/openharmony/applications_app_samples/blob/master/code/DocsSample/ArkTS/JSVMAPI/JsvmUsageGuide/JsvmAboutClass/defineclass/src/main/cpp/hello.cpp) --> 190 191**样例JS** 192```cpp 193const char *srcCallNative = R"JS( 194 defineClass(); 195)JS"; 196``` 197**执行结果** 198 199在LOG中输出下面的结果: 200```cpp 201Create Instance 202 203NAPI MyObject::New newTarget != nullptr 204 205NewInstance:{"name":"lilei"} 206 207Create Instance 208 209NAPI MyObject::New newTarget == nullptr 210 211NewInstance:{"name":"lilei"} 212``` 213### OH_JSVM_Wrap 214 215在JavaScript对象中封装原生实例。稍后可以使用OH_JSVM_Unwrap()解包原生实例 216 217### OH_JSVM_Unwrap 218 219解包JavaScript对象中先前封装的原生实例 220 221### OH_JSVM_RemoveWrap 222 223解包先前封装在JavaScript对象中的原生实例并释放封装 224 225cpp部分代码 226 227```cpp 228// hello.cpp 229#include <string> 230 231// OH_JSVM_GetNewTarget、OH_JSVM_DefineClass、OH_JSVM_Wrap、OH_JSVM_Unwrap、OH_JSVM_RemoveWrap的样例方法 232 233// 自定义类结构体Object 234struct Object { 235 std::string name; 236 int32_t age; 237}; 238 239// 定义一个回调函数 240static void DerefItem(JSVM_Env env, void *data, void *hint) { 241 OH_LOG_INFO(LOG_APP, "JSVM deref_item"); 242 (void)hint; 243} 244 245static JSVM_Value WrapObject(JSVM_Env env, JSVM_CallbackInfo info) { 246 OH_LOG_INFO(LOG_APP, "JSVM wrap"); 247 Object obj; 248 // 设置Object属性 249 obj.name = "lilei"; 250 obj.age = 18; 251 Object *objPointer = &obj; 252 // 获取回调信息中的参数数量和将要被封装的值 253 size_t argc = 1; 254 JSVM_Value toWrap = nullptr; 255 JSVM_CALL(OH_JSVM_GetCbInfo(env, info, &argc, &toWrap, nullptr, nullptr)); 256 // OH_JSVM_Wrap将自定义结构Object进行封装 257 JSVM_CALL(OH_JSVM_Wrap(env, toWrap, reinterpret_cast<void *>(objPointer), DerefItem, NULL, NULL)); 258 Object *data; 259 // OH_JSVM_UnWrap解包先前封装在JavaScript对象中的原生实例 260 JSVM_CALL(OH_JSVM_Unwrap(env, toWrap, reinterpret_cast<void **>(&data))); 261 OH_LOG_INFO(LOG_APP, "JSVM name: %{public}s", data->name.c_str()); 262 OH_LOG_INFO(LOG_APP, "JSVM age: %{public}d", data->age); 263 return nullptr; 264} 265 266static JSVM_Value RemoveWrap(JSVM_Env env, JSVM_CallbackInfo info) { 267 OH_LOG_INFO(LOG_APP, "JSVM removeWrap"); 268 Object obj; 269 // 设置Object属性 270 obj.name = "lilei"; 271 obj.age = 18; 272 Object *objPointer = &obj; 273 // 获取回调信息中的参数数量和将要被封装的值 274 size_t argc = 1; 275 JSVM_Value toWrap = nullptr; 276 JSVM_CALL(OH_JSVM_GetCbInfo(env, info, &argc, &toWrap, nullptr, nullptr)); 277 // 将自定义结构Object封装 278 JSVM_CALL(OH_JSVM_Wrap(env, toWrap, reinterpret_cast<void *>(objPointer), DerefItem, NULL, NULL)); 279 Object *data; 280 // 解包先前封装的object,并移除封装 281 JSVM_CALL(OH_JSVM_RemoveWrap(env, toWrap, reinterpret_cast<void **>(&objPointer))); 282 // 检查是否已被移除 283 JSVM_Status status = OH_JSVM_Unwrap(env, toWrap, reinterpret_cast<void **>(&data)); 284 if (status != JSVM_OK) { 285 OH_LOG_INFO(LOG_APP, "JSVM OH_JSVM_RemoveWrap success"); 286 } 287 return nullptr; 288} 289 290// WrapObject、RemoveWrap注册回调 291static JSVM_CallbackStruct param[] = { 292 {.data = nullptr, .callback = WrapObject}, 293 {.data = nullptr, .callback = RemoveWrap}, 294}; 295static JSVM_CallbackStruct *method = param; 296// WrapObject、RemoveWrap方法别名,供JS调用 297static JSVM_PropertyDescriptor descriptor[] = { 298 {"wrapObject", nullptr, method++, nullptr, nullptr, nullptr, JSVM_DEFAULT}, 299 {"removeWrap", nullptr, method++, nullptr, nullptr, nullptr, JSVM_DEFAULT}, 300}; 301``` 302<!-- @[oh_jsvm_removewrap](https://gitcode.com/openharmony/applications_app_samples/blob/master/code/DocsSample/ArkTS/JSVMAPI/JsvmUsageGuide/JsvmAboutClass/removewrap/src/main/cpp/hello.cpp) --> 303 304**样例JS** 305```cpp 306const char *srcCallNative = R"JS( 307 class Obj {}; 308 wrapObject(new Obj()); 309 removeWrap(new Obj()); 310)JS"; 311``` 312**执行结果** 313 314在LOG中输出下面的结果: 315```cpp 316JSVM wrap 317 318JSVM name: lilei 319 320JSVM age: 18 321 322JSVM removeWrap 323 324JSVM OH_JSVM_RemoveWrap success 325 326JSVM deref_item 327``` 328### OH_JSVM_DefineClassWithOptions 329**Note:** 传入的父类class必须是通过OH_JSVM_DefineClass系列接口创建出来的,否则被视为无效参数,返回JSVM_INVALID_ARG错误码。 330目前支持以下的DefineClassOptions: 331- JSVM_DEFINE_CLASS_NORMAL: 按正常模式创建Class。默认缺省状态为JSVM_DEFINE_CLASS_NORMAL状态。 332- JSVM_DEFINE_CLASS_WITH_COUNT: 为所创建的Class预留interfield槽位。 333- JSVM_DEFINE_CLASS_WITH_PROPERTY_HANDLER: 为所创建的Class设置监听拦截属性以及设置作为函数调用时回调函数。 334 335cpp部分代码 336 337```c++ 338#include <string> 339#include <memory> 340static JSVM_PropertyHandlerConfigurationStruct propertyCfg{ 341 nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr 342}; 343 344static bool g_call_as_function_flag = false; 345static bool g_set_named_property_flag = false; 346static bool g_call_as_constructor_flag = false; 347static bool g_properties_flag = false; 348 349static JSVM_Value SetNamedPropertyCbInfo2(JSVM_Env env, JSVM_Value name, JSVM_Value property, JSVM_Value thisArg, 350 JSVM_Value data) 351{ 352 g_set_named_property_flag = true; 353 return property; 354} 355 356static JSVM_Value Add(JSVM_Env env, JSVM_CallbackInfo info) { 357 g_properties_flag = true; 358 size_t argc = 2; 359 JSVM_Value args[2]; 360 OH_JSVM_GetCbInfo(env, info, &argc, args, NULL, NULL); 361 double num1 = 0; 362 double num2 = 0; 363 OH_JSVM_GetValueDouble(env, args[0], &num1); 364 OH_JSVM_GetValueDouble(env, args[1], &num2); 365 JSVM_Value sum = nullptr; 366 OH_JSVM_CreateDouble(env, num1 + num2, &sum); 367 return sum; 368} 369 370std::string ToString(JSVM_Env jsvm_env, JSVM_Value val) 371{ 372 JSVM_Value js_string; 373 OH_JSVM_CoerceToString(jsvm_env, val, &js_string); 374 size_t length = 0; 375 OH_JSVM_GetValueStringUtf8(jsvm_env, js_string, NULL, 0, &length); 376 size_t capacity = length + 1; 377 auto buffer = std::make_unique<char[]>(capacity); 378 size_t copy_length = 0; 379 OH_JSVM_GetValueStringUtf8(jsvm_env, js_string, buffer.get(), capacity, ©_length); 380 std::string str(buffer.get()); 381 return str; 382} 383 384JSVM_Value Run(JSVM_Env env, const char *s) 385{ 386 // 1. 将const char*转换成JS_String。 387 JSVM_Value str; 388 JSVM_CALL(OH_JSVM_CreateStringUtf8(env, s, JSVM_AUTO_LENGTH, &str)); 389 // 2. 将JS_String转换成JS_Script。 390 JSVM_Script script; 391 OH_JSVM_CompileScript(env, str, nullptr, JSVM_AUTO_LENGTH, false, nullptr, &script); 392 // 3. 执行JS_Script。 393 JSVM_Value result = nullptr; 394 OH_JSVM_RunScript(env, script, &result); 395 return result; 396} 397 398static JSVM_Value TestDefineClassWithOptions(JSVM_Env env, JSVM_CallbackInfo info) 399{ 400 g_call_as_function_flag = false; 401 g_set_named_property_flag = false; 402 g_call_as_constructor_flag = false; 403 g_properties_flag = false; 404 // 1. Define parent-class. 405 JSVM_Value parentClass = nullptr; 406 JSVM_CallbackStruct parentClassConstructor; 407 parentClassConstructor.data = nullptr; 408 parentClassConstructor.callback = [](JSVM_Env env, JSVM_CallbackInfo info) -> JSVM_Value { 409 JSVM_Value thisVar = nullptr; 410 OH_JSVM_GetCbInfo(env, info, nullptr, nullptr, &thisVar, nullptr); 411 return thisVar; 412 }; 413 JSVM_Value fooVal; 414 OH_JSVM_CreateStringUtf8(env, "bar", JSVM_AUTO_LENGTH, &fooVal); 415 JSVM_PropertyDescriptor des[2]; 416 des[0] = { 417 .utf8name = "foo", 418 .value = fooVal, 419 }; 420 JSVM_CallbackStruct parentProperties[] = { 421 {.callback = Add, .data = nullptr}, 422 }; 423 des[1] = { 424 .utf8name = "add", 425 .method = &parentProperties[0], 426 }; 427 JSVM_DefineClassOptions options[1]; 428 options[0].id = JSVM_DEFINE_CLASS_WITH_COUNT; 429 options[0].content.num = 3; 430 JSVM_CALL(OH_JSVM_DefineClassWithOptions(env, "parentClass", JSVM_AUTO_LENGTH, &parentClassConstructor, 2, des, 431 nullptr, 1, options, &parentClass)); 432 433 // 2. Define sub-class. 434 JSVM_Value subClass = nullptr; 435 JSVM_CallbackStruct subClassConstructor; 436 subClassConstructor.data = nullptr; 437 subClassConstructor.callback = [](JSVM_Env env, JSVM_CallbackInfo info) -> JSVM_Value { 438 JSVM_Value thisVar = nullptr; 439 g_call_as_constructor_flag = true; 440 OH_JSVM_GetCbInfo(env, info, nullptr, nullptr, &thisVar, nullptr); 441 return thisVar; 442 }; 443 JSVM_DefineClassOptions subOptions[2]; 444 JSVM_CallbackStruct callAsFuncParam; 445 callAsFuncParam.data = nullptr; 446 callAsFuncParam.callback = [](JSVM_Env env, JSVM_CallbackInfo info) -> JSVM_Value { 447 JSVM_Value thisVar = nullptr; 448 g_call_as_function_flag = true; 449 OH_JSVM_GetCbInfo(env, info, nullptr, nullptr, &thisVar, nullptr); 450 return thisVar; 451 }; 452 propertyCfg.genericNamedPropertySetterCallback = SetNamedPropertyCbInfo2; 453 JSVM_PropertyHandler propertyHandler = { 454 .propertyHandlerCfg = &propertyCfg, 455 .callAsFunctionCallback = &callAsFuncParam, 456 }; 457 subOptions[0].id = JSVM_DEFINE_CLASS_WITH_COUNT; 458 subOptions[0].content.num = 4; 459 subOptions[1].id = JSVM_DEFINE_CLASS_WITH_PROPERTY_HANDLER; 460 subOptions[1].content.ptr = &propertyHandler; 461 JSVM_CALL(OH_JSVM_DefineClassWithOptions(env, "subClass", JSVM_AUTO_LENGTH, &subClassConstructor, 0, nullptr, 462 parentClass, 2, subOptions, &subClass)); 463 // 3. Verify the validity of 'constructor'. 464 JSVM_Value subInstance; 465 JSVM_CALL(OH_JSVM_NewInstance(env, subClass, 0, nullptr, &subInstance)); 466 467 JSVM_Value globalVal; 468 OH_JSVM_GetGlobal(env, &globalVal); 469 OH_JSVM_SetNamedProperty(env, globalVal, "obj", subInstance); 470 471 // 4. Verify the validity of 'parentClass'. 472 JSVM_Value subRes = nullptr; 473 JSVM_CALL(OH_JSVM_GetNamedProperty(env, subInstance, "foo", &subRes)); 474 if (ToString(env, subRes).compare("bar") != 0) { 475 OH_LOG_ERROR(LOG_APP, "Run OH_JSVM_DefineClassWithOptions: Failed"); 476 } 477 // 5. Verify the validity of 'properties'. 478 Run(env, "obj.add(3, 4);"); 479 // 6. Verify the validity of 'options'. 480 Run(env, "obj()"); 481 Run(env, "obj.x = 123;"); 482 if (g_call_as_function_flag && 483 g_set_named_property_flag && 484 g_call_as_constructor_flag && 485 g_properties_flag) { 486 OH_LOG_INFO(LOG_APP, "Run OH_JSVM_DefineClassWithOptions: Success"); 487 } else { 488 OH_LOG_ERROR(LOG_APP, "Run OH_JSVM_DefineClassWithOptions: Failed"); 489 } 490 JSVM_Value checked; 491 OH_JSVM_GetBoolean(env, true, &checked); 492 return checked; 493} 494 495static JSVM_CallbackStruct param[] = { 496 {.data = nullptr, .callback = TestDefineClassWithOptions}, 497}; 498static JSVM_CallbackStruct *method = param; 499 500static JSVM_PropertyDescriptor descriptor[] = { 501 {"testDefineClassWithOptions", nullptr, method++, nullptr, nullptr, nullptr, JSVM_DEFAULT}, 502}; 503 504``` 505**样例JS** 506```cpp 507const char *srcCallNative = R"JS(testDefineClassWithOptions();)JS"; 508``` 509**执行结果** 510 511在LOG中输出下面的结果: 512```cpp 513Run OH_JSVM_DefineClassWithOptions: Success 514```