绑定全局变量
我们首先从最简单的全局变量绑定开始。
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
进行的。这里步骤如下:
- 首先使用
JS_NewInt32
创建一个整数类型的JSValue
- 做异常检查
- 得到全局对象
JS_GetGlobalObject
- 使用
JS_SetPropertyStr
将我们的变量注册到全局对象中 - 清理内存
重点是注册的函数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)
绑定基础类型变量的规则
绑定对象的步骤如下:
- 使用
JS_NewXXX
来创建一个JS对象 - 使用
JS_SetPropertyStr
来将对象绑定在另一个JS对象中 - 清理内存
可创建的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_id
,class_id
是类的唯一标识,在你注册类的时候会生成一个(等待后面绑定类的时候会说到)。
然后使用JS_SetOpaque
函数将我们的类对象塞给JSValue
即可。
绑定空对象
使用JS_NewObject
创建一个空对象(即创建一个JS中的Object
实例)。这个对象不和任何类相关联,所以也不需要一个class_id
。
内置JSValue常量
JS_UNDEFINED
JS_NULL
这两个是内置的字面常量,可以直接使用无需JS_NewXXX
。