• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.target122    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, &param, 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, &copy_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```