Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

绑定全局变量

Github代码

我们首先从最简单的全局变量绑定开始。

QuickJS的类型

QuickJS中使用JSValue包装所有C/C++类型。支持的类型如下:

  • Number:数字类型
  • String:字符串类型(是C风格字符串不是std::string
  • Boolean:布尔值
  • Class:类
  • Module:模块
  • Exception:异常
  • Function:函数
  • Array:数组

例子

现在希望绑定一个数值类型的变量:

int gGlobalVar = 123;

做法如下:

void Bind(JSContext* ctx) {
    // Int32 value is directly copied into JSValue(no malloc), so we don't need JS_FreeValue it
    JSValue new_obj = JS_NewInt32(ctx, gGlobalVar);
    if (JS_IsException(new_obj)) {
        js_std_dump_error(ctx);
        JS_FreeValue(ctx, new_obj);
        return;
    }

    JSValue global_this = JS_GetGlobalObject(ctx);

    // JS_WRITABLE | JS_ENUMERABLE | JS_CONFIGURABLE by default
    JS_SetPropertyStr(ctx, global_this, "global_var", new_obj);

    // don't forget cleanup
    JS_FreeValue(ctx, global_this);
}

C++和JS的所有数据交换都是通过JSValue进行的。这里步骤如下:

  1. 首先使用JS_NewInt32创建一个整数类型的JSValue
  2. 做异常检查
  3. 得到全局对象JS_GetGlobalObject
  4. 使用JS_SetPropertyStr将我们的变量注册到全局对象中
  5. 清理内存

重点是注册的函数JS_SetPropertyStr。其原型如下:

int JS_SetPropertyStr(JSContext *ctx, JSValueConst this_obj, const char *prop, JSValue val)
  • this_object:要注册到的对象
  • prop:要注册的对象名称(在JS中使用的名称)
  • val:要注册的对象

注册完之后就可以在JS中使用了:

console.log("global_var: ", global_var)

绑定基础类型变量的规则

绑定对象的步骤如下:

  1. 使用JS_NewXXX来创建一个JS对象
  2. 使用JS_SetPropertyStr来将对象绑定在另一个JS对象中
  3. 清理内存

可创建的JS对象

  • 通用的数值类创建使用JS_NewNumber。细分如下:
    • JS_NewInt32:创建32位的整数
    • JS_NewInt64:创建64位整数(底层规则是:如果是32位,调用JS_NewInt32,否则直接调用JS_NewFloat64存在64位浮点数中)
    • JS_NewUint32:创建无符号32位整数(规则通JS_NewInt64
    • JS_NewFloat64:创建double类型(JS中没有float类型都是double)
  • 大数类型:
    • JS_NewBigInt64
    • JS_NewBigUInt64
  • JS_NewBool:创建布尔值
  • JS_NewClass:创建类
  • JS_NewObject:创建对象
  • 字符串类:
    • JS_NewString
    • JS_NewStringLen:可指定字符串长度

何时释放JSValue?

JSValue的释放也是有讲究的。其底层实现为:

void JS_FreeValueRT(JSRuntime *rt, JSValue v)
{
    if (JS_VALUE_HAS_REF_COUNT(v)) {
        JSRefCountHeader *p = (JSRefCountHeader *)JS_VALUE_GET_PTR(v);
        if (--p->ref_count <= 0) {
            js_free_value_rt(rt, v);
        }
    }
}

是先判断是否有引用计数,如果有的话,当计数降为0释放内存。

有引用计数的一般是类和对象类型。像基础的数值类型是直接值拷贝入JSValue的,也不会有内存分配/释放:

// JSValue impl
typedef union JSValueUnion {
    int32_t int32;
    double float64;
    void *ptr;
    int32_t short_big_int;
} JSValueUnion;

typedef struct JSValue {
    JSValueUnion u;
    int64_t tag;
} JSValue;
  • tag:即JS_TAG_XXX类型,标识JSValue的类型
  • u:如果是数值类型,就记录在非ptr中并且没有内存分配。否则进行内存分配,将指针记录在ptr

JS_VALUE_HAS_REF_COUNT也让我们知道哪些是会进行内存分配的:

#define JS_VALUE_HAS_REF_COUNT(v) ((unsigned)JS_VALUE_GET_TAG(v) >= (unsigned)JS_TAG_FIRST)

enum {
    // has memory allocation
    JS_TAG_FIRST       = -9, /* first negative tag */
    JS_TAG_BIG_INT     = -9,
    JS_TAG_SYMBOL      = -8,
    JS_TAG_STRING      = -7,
    JS_TAG_MODULE      = -3, /* used internally */
    JS_TAG_FUNCTION_BYTECODE = -2, /* used internally */
    JS_TAG_OBJECT      = -1,

    // no memory allocation 
    JS_TAG_INT         = 0,
    JS_TAG_BOOL        = 1,
    JS_TAG_NULL        = 2,
    JS_TAG_UNDEFINED   = 3,
    JS_TAG_UNINITIALIZED = 4,
    JS_TAG_CATCH_OFFSET = 5,
    JS_TAG_EXCEPTION   = 6,
    JS_TAG_SHORT_BIG_INT = 7,
    JS_TAG_FLOAT64     = 8,
};

那么所有时候,只要使用了JS_NewXXX就一定要调用JS_FreeValue吗?答案是否定的。具体是否需要释放要看之后的函数是否影响了其引用计数。比如上面HelloWorld中的代码我们就没有释放new_obj。因为new_obj被创建出来时引用计数是1。而JS_SetPropertyStr传入new_obj后是不会改变其引用计数的。这个时候如果调用了JS_FreeValue则会将new_obj的引用计数降为0,进而释放内存。但此时其已被注册在global_this中了。在JSRuntime释放的时候会尝试释放所有被引用的节点,这个时候会再次释放new_obj造成程序崩溃。

所以是否要释放JSValue,关键在于之后使用的函数是否增加了JSValue的引用计数。如果增加了就需要释放。

或者说的更严谨一点,当其他函数内部使用了js_dup()时就是增加了引用计数,这个时候我们就得手动释放。

绑定类对象

如果想要绑定类对象,需要使用JS_NewClassObject并且将类对象设置进去:

Person* p = new Person{}; // C++ class object
JSValue result = JS_NewObjectClass(ctx, class_id);
JS_SetOpaque(result, person);
JS_SetPropertyStr(ctx, global_this, "person", result);

这里需要一个class_idclass_id是类的唯一标识,在你注册类的时候会生成一个(等待后面绑定类的时候会说到)。

然后使用JS_SetOpaque函数将我们的类对象塞给JSValue即可。

绑定空对象

使用JS_NewObject创建一个空对象(即创建一个JS中的Object实例)。这个对象不和任何类相关联,所以也不需要一个class_id

内置JSValue常量

  • JS_UNDEFINED
  • JS_NULL

这两个是内置的字面常量,可以直接使用无需JS_NewXXX