绑定全局变量
我们首先从最简单的全局变量绑定开始。
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_NewBigInt64JS_NewBigUInt64
JS_NewBool:创建布尔值JS_NewClass:创建类JS_NewObject:创建对象- 字符串类:
JS_NewStringJS_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_UNDEFINEDJS_NULL
这两个是内置的字面常量,可以直接使用无需JS_NewXXX。