使用JSVM-API接口进行class相关开发
=======================

#### 简介

使用JSVM-API接口进行class相关开发，处理JavaScript中的类，例如定义类、构造实例等。

#### 基本概念

在使用JSVM-API接口进行class相关开发时，需要理解以下基本概念：

*   **类**：类是用于创建对象的模板。它提供了一种封装数据和行为的方式，以便于对数据进行处理和操作。类在JavaScript中是建立在原型（prototype）的基础上的，并且还引入了一些类独有的语法和语义。
*   **实例**：实例是通过类创建具体的对象。类定义了对象的结构和行为，而实例则是类的具体表现。通过实例化类，我们可以访问类中定义的属性和方法，并且每个实例都具有自己的属性值。

#### 接口说明

| 接口 | 功能说明 |
| :-- | :-- |
| OH\_JSVM\_NewInstance | 通过给定的构造函数，创建一个实例。 |
| OH\_JSVM\_GetNewTarget | 获取函数的元属性new.target。 |
| OH\_JSVM\_DefineClass | 用于在JavaScript中定义一个类，并与对应的C类进行封装和交互。它提供了创建类的构造函数、定义属性和方法的能力，支持C和JavaScript之间的数据交互。 |
| OH\_JSVM\_Wrap | 在JavaScript对象中封装原生实例。稍后可以使用OH\_JSVM\_Unwrap()解包原生实例。 |
| OH\_JSVM\_Unwrap | 解包先前封装在JavaScript对象中的原生实例。 |
| OH\_JSVM\_RemoveWrap | 解包先前封装在JavaScript对象中的原生实例，并释放封装。 |
| OH\_JSVM\_DefineClassWithOptions | 定义一个具有给定类名、构造函数、属性和回调处理程序、父类的JavaScript类，并根据传入了DefineClassOptions来决定是否需要为所定义的Class设置属性代理、预留internal-field槽位、为class作为函数进行调用时设置函数回调。 |

#### 使用示例

JSVM-API接口开发流程参考[使用JSVM-API实现JS与C/C++语言交互开发流程](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/use-jsvm-process)，本文仅对接口对应C++相关代码进行展示。

#### \[h2\]OH\_JSVM\_NewInstance

通过给定的构造函数，构建一个实例。

cpp部分代码

// hello.cpp
#include <string.h>
#include <fstream>

std::string ToString(JSVM\_Env env, JSVM\_Value val) {
    JSVM\_Value jsonString = nullptr;
    JSVM\_CALL(OH\_JSVM\_JsonStringify(env, val, &jsonString));
    size\_t totalLen = 0;
    JSVM\_CALL(OH\_JSVM\_GetValueStringUtf8(env, jsonString, nullptr, 0, &totalLen));
    size\_t needLen = totalLen + 1;
    char\* buff = new char\[needLen\];
    std::memset(buff, 0, needLen);
    JSVM\_CALL(OH\_JSVM\_GetValueStringUtf8(env, jsonString, buff, needLen, &totalLen));
    std::string str(buff);
    delete\[\] buff;
    return str;
}

// OH\_JSVM\_NewInstance的样例方法
static JSVM\_Value NewInstance(JSVM\_Env env, JSVM\_CallbackInfo info) {
    // 获取js侧传入的两个参数
    size\_t argc = 2;
    JSVM\_Value args\[2\] = {nullptr};
    JSVM\_CALL(OH\_JSVM\_GetCbInfo(env, info, &argc, args, nullptr, nullptr));
    JSVM\_Value result = nullptr;
    // 调用OH\_JSVM\_NewInstance接口，实例化一个对象，将这个对象返回
    JSVM\_CALL(OH\_JSVM\_NewInstance(env, args\[0\], 1, &args\[1\], &result));
    std::string str = ToString(env, result);
    OH\_LOG\_INFO(LOG\_APP, "NewInstance:%{public}s", str.c\_str());
    return nullptr;
}

// 通过给定的构造函数，构建一个实例。
// NewInstance注册回调
static JSVM\_CallbackStruct param\[\] = {
    {.data = nullptr, .callback = NewInstance},
};

static JSVM\_CallbackStruct \*method = param;

// NewInstance方法别名，供JS调用
static JSVM\_PropertyDescriptor descriptor\[\] = {
    {"newInstance", nullptr, method++, nullptr, nullptr, nullptr, JSVM\_DEFAULT},
};

**样例JS**

const char \*srcCallNative = R"JS(
   function Fruit(name) {
       this.name = name;
   }
   newInstance(Fruit, "apple");
)JS";

**执行结果**

在LOG中输出下面的结果：

NewInstance:{"name":"apple"}

#### \[h2\]OH\_JSVM\_GetNewTarget

用于获取函数的元属性new.target值。在JavaScript中，new.target是一个特殊的元属性，用于检测函数或构造函数是否是通过 'new' 运算符被调用的。

#### \[h2\]OH\_JSVM\_DefineClass

用于在JavaScript中定义一个类，并与对应的C类进行封装和交互。它提供了创建类的构造函数、定义属性和方法的能力，以及在C和JavaScript之间进行数据交互的支持。

cpp部分代码

// hello.cpp
#include <string>

JSVM\_Value CreateInstance(JSVM\_Env env, JSVM\_CallbackInfo info) {
    JSVM\_Value newTarget;
    // 获取构造函数的new.target值
    JSVM\_CALL(OH\_JSVM\_GetNewTarget(env, info, &newTarget));
    OH\_LOG\_INFO(LOG\_APP, "Create Instance");
    OH\_LOG\_INFO(LOG\_APP, "NAPI MyObject::New %{public}s", newTarget != nullptr ? "newTarget != nullptr" : "newTarget == nullptr");
    JSVM\_Value jsObject = nullptr;
    JSVM\_CALL(OH\_JSVM\_CreateObject(env, &jsObject));
    JSVM\_Value jsName = nullptr;
    JSVM\_CALL(OH\_JSVM\_CreateStringUtf8(env, "name", JSVM\_AUTO\_LENGTH, &jsName));
    JSVM\_Value jsValue = nullptr;
    JSVM\_CALL(OH\_JSVM\_CreateStringUtf8(env, "lilei", JSVM\_AUTO\_LENGTH, &jsValue));
    JSVM\_CALL(OH\_JSVM\_SetProperty(env, jsObject, jsName, jsValue));
    return jsObject;
}

std::string ToString(JSVM\_Env env, JSVM\_Value val) {
    JSVM\_Value jsonString = nullptr;
    JSVM\_CALL(OH\_JSVM\_JsonStringify(env, val, &jsonString));
    size\_t totalLen = 0;
    JSVM\_CALL(OH\_JSVM\_GetValueStringUtf8(env, jsonString, nullptr, 0, &totalLen));
    size\_t needLen = totalLen + 1;
    char\* buff = new char\[needLen\];
    std::memset(buff, 0, needLen);
    JSVM\_CALL(OH\_JSVM\_GetValueStringUtf8(env, jsonString, buff, needLen, &totalLen));
    std::string str(buff);
    delete\[\] buff;
    return str;
}

// 封装c++中的自定义数据结构
JSVM\_Value DefineClass(JSVM\_Env env, JSVM\_CallbackInfo info) {
    JSVM\_CallbackStruct param;
    param.data = nullptr;
    param.callback = CreateInstance;
    JSVM\_Value cons;
    // 用于在JavaScript中定义一个类
    JSVM\_CALL(OH\_JSVM\_DefineClass(env, "MyObject", JSVM\_AUTO\_LENGTH, &param, 0, nullptr, &cons));
    JSVM\_Value instanceValue = nullptr;
    // 作为class的构造函数调用
    JSVM\_CALL(OH\_JSVM\_NewInstance(env, cons, 0, nullptr, &instanceValue));
    std::string str = ToString(env, instanceValue);
    OH\_LOG\_INFO(LOG\_APP, "NewInstance:%{public}s", str.c\_str());
    
    // 作为普通的函数调用
    JSVM\_Value global = nullptr;
    JSVM\_CALL(OH\_JSVM\_GetGlobal(env, &global));
    JSVM\_Value key;
    JSVM\_CALL(OH\_JSVM\_CreateStringUtf8(env, "Constructor", JSVM\_AUTO\_LENGTH, &key));
    JSVM\_CALL(OH\_JSVM\_SetProperty(env, global, key, cons));
    JSVM\_Value result = nullptr;
    JSVM\_CALL(OH\_JSVM\_CallFunction(env, global, cons, 0, nullptr, &result));
    std::string buf = ToString(env, result);
    OH\_LOG\_INFO(LOG\_APP, "NewInstance:%{public}s", buf.c\_str());
    return nullptr;
}

// 注册DefineClass的方法
JSVM\_CallbackStruct param\[\] = {
    {.data = nullptr, .callback = DefineClass},
};

static JSVM\_CallbackStruct \*method = param;

// DefineClass方法别名，供JS调用
static JSVM\_PropertyDescriptor descriptor\[\] = {
    {"defineClass", nullptr, method++, nullptr, nullptr, nullptr, JSVM\_DEFAULT},
};

**样例JS**

const char \*srcCallNative = R"JS(
    defineClass();
)JS";

**执行结果**

在LOG中输出下面的结果：

Create Instance

NAPI MyObject::New newTarget != nullptr

NewInstance:{"name":"lilei"}

Create Instance

NAPI MyObject::New newTarget == nullptr

NewInstance:{"name":"lilei"}

#### \[h2\]OH\_JSVM\_Wrap

在JavaScript对象中封装原生实例。稍后可以使用OH\_JSVM\_Unwrap()解包原生实例

#### \[h2\]OH\_JSVM\_Unwrap

解包JavaScript对象中先前封装的原生实例

#### \[h2\]OH\_JSVM\_RemoveWrap

解包先前封装在JavaScript对象中的原生实例并释放封装

cpp部分代码

// hello.cpp
#include <string>

// OH\_JSVM\_GetNewTarget、OH\_JSVM\_DefineClass、OH\_JSVM\_Wrap、OH\_JSVM\_Unwrap、OH\_JSVM\_RemoveWrap的样例方法

// 自定义类结构体Object
struct Object {
    std::string name;
    int32\_t age;
};

// 定义一个回调函数
static void DerefItem(JSVM\_Env env, void \*data, void \*hint) {
    OH\_LOG\_INFO(LOG\_APP, "JSVM deref\_item");
    (void)hint;
}

static JSVM\_Value WrapObject(JSVM\_Env env, JSVM\_CallbackInfo info) {
    OH\_LOG\_INFO(LOG\_APP, "JSVM wrap");
    Object obj;
    // 设置Object属性
    obj.name = "lilei";
    obj.age = 18;
    Object \*objPointer = &obj;
    // 获取回调信息中的参数数量和将要被封装的值
    size\_t argc = 1;
    JSVM\_Value toWrap = nullptr;
    JSVM\_CALL(OH\_JSVM\_GetCbInfo(env, info, &argc, &toWrap, nullptr, nullptr));
    // OH\_JSVM\_Wrap将自定义结构Object进行封装
    JSVM\_CALL(OH\_JSVM\_Wrap(env, toWrap, reinterpret\_cast<void \*>(objPointer), DerefItem, NULL, NULL));
    Object \*data;
    // OH\_JSVM\_UnWrap解包先前封装在JavaScript对象中的原生实例
    JSVM\_CALL(OH\_JSVM\_Unwrap(env, toWrap, reinterpret\_cast<void \*\*>(&data)));
    OH\_LOG\_INFO(LOG\_APP, "JSVM name: %{public}s", data->name.c\_str());
    OH\_LOG\_INFO(LOG\_APP, "JSVM age: %{public}d", data->age);
    return nullptr;
}

static JSVM\_Value RemoveWrap(JSVM\_Env env, JSVM\_CallbackInfo info) {
    OH\_LOG\_INFO(LOG\_APP, "JSVM removeWrap");
    Object obj;
    // 设置Object属性
    obj.name = "lilei";
    obj.age = 18;
    Object \*objPointer = &obj;
    // 获取回调信息中的参数数量和将要被封装的值
    size\_t argc = 1;
    JSVM\_Value toWrap = nullptr;
    JSVM\_CALL(OH\_JSVM\_GetCbInfo(env, info, &argc, &toWrap, nullptr, nullptr));
    // 将自定义结构Object封装
    JSVM\_CALL(OH\_JSVM\_Wrap(env, toWrap, reinterpret\_cast<void \*>(objPointer), DerefItem, NULL, NULL));
    Object \*data;
    // 解包先前封装的object，并移除封装
    JSVM\_CALL(OH\_JSVM\_RemoveWrap(env, toWrap, reinterpret\_cast<void \*\*>(&objPointer)));
    // 检查是否已被移除
    JSVM\_Status status = OH\_JSVM\_Unwrap(env, toWrap, reinterpret\_cast<void \*\*>(&data));
    if (status != JSVM\_OK) {
        OH\_LOG\_INFO(LOG\_APP, "JSVM OH\_JSVM\_RemoveWrap success");
    }
    return nullptr;
}

// WrapObject、RemoveWrap注册回调
static JSVM\_CallbackStruct param\[\] = {
    {.data = nullptr, .callback = WrapObject},
    {.data = nullptr, .callback = RemoveWrap},
};
static JSVM\_CallbackStruct \*method = param;
// WrapObject、RemoveWrap方法别名，供JS调用
static JSVM\_PropertyDescriptor descriptor\[\] = {
    {"wrapObject", nullptr, method++, nullptr, nullptr, nullptr, JSVM\_DEFAULT},
    {"removeWrap", nullptr, method++, nullptr, nullptr, nullptr, JSVM\_DEFAULT},
};

**样例JS**

const char \*srcCallNative = R"JS(
    class Obj {};
    wrapObject(new Obj());
    removeWrap(new Obj());
)JS";

**执行结果**

在LOG中输出下面的结果：

JSVM wrap

JSVM name: lilei

JSVM age: 18

JSVM removeWrap

JSVM OH\_JSVM\_RemoveWrap success

JSVM deref\_item

#### \[h2\]OH\_JSVM\_DefineClassWithOptions

**Note:**

传入的父类class必须是通过OH\_JSVM\_DefineClass系列接口创建出来的，否则被视为无效参数，返回JSVM\_INVALID\_ARG错误码。

目前支持以下的DefineClassOptions:

*   JSVM\_DEFINE\_CLASS\_NORMAL: 按正常模式创建Class。默认缺省状态为JSVM\_DEFINE\_CLASS\_NORMAL状态。
*   JSVM\_DEFINE\_CLASS\_WITH\_COUNT: 为所创建的Class预留interfield槽位。
*   JSVM\_DEFINE\_CLASS\_WITH\_PROPERTY\_HANDLER: 为所创建的Class设置监听拦截属性以及设置作为函数调用时回调函数。

cpp部分代码

#include <string>
#include <memory>
static JSVM\_PropertyHandlerConfigurationStruct propertyCfg{
  nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr
};

static bool g\_call\_as\_function\_flag = false;
static bool g\_set\_named\_property\_flag = false;
static bool g\_call\_as\_constructor\_flag = false;
static bool g\_properties\_flag = false;

static JSVM\_Value SetNamedPropertyCbInfo2(JSVM\_Env env, JSVM\_Value name, JSVM\_Value property, JSVM\_Value thisArg,
    JSVM\_Value data)
{
    g\_set\_named\_property\_flag = true;
    return property;
}

static JSVM\_Value Add(JSVM\_Env env, JSVM\_CallbackInfo info) {
    g\_properties\_flag = true;
    size\_t argc = 2;
    JSVM\_Value args\[2\];
    OH\_JSVM\_GetCbInfo(env, info, &argc, args, NULL, NULL);
    double num1 = 0;
    double num2 = 0;
    OH\_JSVM\_GetValueDouble(env, args\[0\], &num1);
    OH\_JSVM\_GetValueDouble(env, args\[1\], &num2);
    JSVM\_Value sum = nullptr;
    OH\_JSVM\_CreateDouble(env, num1 + num2, &sum);
    return sum;
}

std::string ToString(JSVM\_Env jsvm\_env, JSVM\_Value val)
{
    JSVM\_Value js\_string;
    OH\_JSVM\_CoerceToString(jsvm\_env, val, &js\_string);
    size\_t length = 0;
    OH\_JSVM\_GetValueStringUtf8(jsvm\_env, js\_string, NULL, 0, &length);
    size\_t capacity = length + 1;
    auto buffer = std::make\_unique<char\[\]>(capacity);
    size\_t copy\_length = 0;
    OH\_JSVM\_GetValueStringUtf8(jsvm\_env, js\_string, buffer.get(), capacity, &copy\_length);
    std::string str(buffer.get());
    return str;
}

JSVM\_Value Run(JSVM\_Env env, const char \*s)
{
    // 1. 将const char\*转换成JS\_String。
    JSVM\_Value str;
    JSVM\_CALL(OH\_JSVM\_CreateStringUtf8(env, s, JSVM\_AUTO\_LENGTH, &str));
    // 2. 将JS\_String转换成JS\_Script。
    JSVM\_Script script;
    OH\_JSVM\_CompileScript(env, str, nullptr, JSVM\_AUTO\_LENGTH,   false, nullptr, &script);
    // 3. 执行JS\_Script。
    JSVM\_Value result = nullptr;
    OH\_JSVM\_RunScript(env, script, &result);
    return result;
}

static JSVM\_Value TestDefineClassWithOptions(JSVM\_Env env, JSVM\_CallbackInfo info)
{
    g\_call\_as\_function\_flag = false;
    g\_set\_named\_property\_flag = false;
    g\_call\_as\_constructor\_flag = false;
    g\_properties\_flag = false;
    // 1. Define parent-class.
    JSVM\_Value parentClass = nullptr;
    JSVM\_CallbackStruct parentClassConstructor;
    parentClassConstructor.data = nullptr;
    parentClassConstructor.callback = \[\](JSVM\_Env env, JSVM\_CallbackInfo info) -> JSVM\_Value {
        JSVM\_Value thisVar = nullptr;
        OH\_JSVM\_GetCbInfo(env, info, nullptr, nullptr, &thisVar, nullptr);
        return thisVar;
    };
    JSVM\_Value fooVal;
    OH\_JSVM\_CreateStringUtf8(env, "bar", JSVM\_AUTO\_LENGTH, &fooVal);
    JSVM\_PropertyDescriptor des\[2\];
    des\[0\] = {
        .utf8name = "foo",
        .value = fooVal,
    };
    JSVM\_CallbackStruct parentProperties\[\] = {
        {.callback = Add, .data = nullptr},
    };
    des\[1\] = {
        .utf8name = "add",
        .method = &parentProperties\[0\],
    };
    JSVM\_DefineClassOptions options\[1\];
    options\[0\].id = JSVM\_DEFINE\_CLASS\_WITH\_COUNT;
    options\[0\].content.num = 3;
    JSVM\_CALL(OH\_JSVM\_DefineClassWithOptions(env, "parentClass", JSVM\_AUTO\_LENGTH, &parentClassConstructor, 2, des,
        nullptr, 1, options, &parentClass));
  
    // 2. Define sub-class.
    JSVM\_Value subClass = nullptr;
    JSVM\_CallbackStruct subClassConstructor;
    subClassConstructor.data = nullptr;
    subClassConstructor.callback = \[\](JSVM\_Env env, JSVM\_CallbackInfo info) -> JSVM\_Value {
        JSVM\_Value thisVar = nullptr;
        g\_call\_as\_constructor\_flag = true;
        OH\_JSVM\_GetCbInfo(env, info, nullptr, nullptr, &thisVar, nullptr);
        return thisVar;
    };
    JSVM\_DefineClassOptions subOptions\[2\];
    JSVM\_CallbackStruct callAsFuncParam;
    callAsFuncParam.data = nullptr;
    callAsFuncParam.callback = \[\](JSVM\_Env env, JSVM\_CallbackInfo info) -> JSVM\_Value {
        JSVM\_Value thisVar = nullptr;
        g\_call\_as\_function\_flag = true;
        OH\_JSVM\_GetCbInfo(env, info, nullptr, nullptr, &thisVar, nullptr);
        return thisVar;
    };
    propertyCfg.genericNamedPropertySetterCallback = SetNamedPropertyCbInfo2;
    JSVM\_PropertyHandler propertyHandler = {
        .propertyHandlerCfg = &propertyCfg,
        .callAsFunctionCallback = &callAsFuncParam,
    };
    subOptions\[0\].id = JSVM\_DEFINE\_CLASS\_WITH\_COUNT;
    subOptions\[0\].content.num = 4;
    subOptions\[1\].id = JSVM\_DEFINE\_CLASS\_WITH\_PROPERTY\_HANDLER;
    subOptions\[1\].content.ptr = &propertyHandler;
    JSVM\_CALL(OH\_JSVM\_DefineClassWithOptions(env, "subClass", JSVM\_AUTO\_LENGTH, &subClassConstructor, 0, nullptr,
        parentClass, 2, subOptions, &subClass));
    // 3. Verify the validity of 'constructor'.
    JSVM\_Value subInstance;
    JSVM\_CALL(OH\_JSVM\_NewInstance(env, subClass, 0, nullptr, &subInstance));

    JSVM\_Value globalVal;
    OH\_JSVM\_GetGlobal(env, &globalVal);
    OH\_JSVM\_SetNamedProperty(env, globalVal, "obj", subInstance);

    // 4. Verify the validity of 'parentClass'.
    JSVM\_Value subRes = nullptr;
    JSVM\_CALL(OH\_JSVM\_GetNamedProperty(env, subInstance, "foo", &subRes));
    if (ToString(env, subRes).compare("bar") != 0) {
        OH\_LOG\_ERROR(LOG\_APP, "Run OH\_JSVM\_DefineClassWithOptions: Failed");
    }
    // 5. Verify the validity of 'properties'.
    Run(env, "obj.add(3, 4);");
    // 6. Verify the validity of 'options'.
    Run(env, "obj()");
    Run(env, "obj.x = 123;");
    if (g\_call\_as\_function\_flag &&
    g\_set\_named\_property\_flag &&
    g\_call\_as\_constructor\_flag &&
    g\_properties\_flag) {
        OH\_LOG\_INFO(LOG\_APP, "Run OH\_JSVM\_DefineClassWithOptions: Success");
    } else {
        OH\_LOG\_ERROR(LOG\_APP, "Run OH\_JSVM\_DefineClassWithOptions: Failed");
    }
    JSVM\_Value checked;
    OH\_JSVM\_GetBoolean(env, true, &checked);
    return checked;
}

static JSVM\_CallbackStruct param\[\] = {
    {.data = nullptr, .callback = TestDefineClassWithOptions},
};
static JSVM\_CallbackStruct \*method = param;

static JSVM\_PropertyDescriptor descriptor\[\] = {
    {"testDefineClassWithOptions", nullptr, method++, nullptr, nullptr, nullptr, JSVM\_DEFAULT},
};

**样例JS**

const char \*srcCallNative = R"JS(testDefineClassWithOptions();)JS";

**执行结果**

在LOG中输出下面的结果：

Run OH\_JSVM\_DefineClassWithOptions: Success