如何使用QuickJS将C/C++变量、结构体暴露为JS全局变量并同步
单个int变量暴露实现
核心用JS属性的getter/setter机制,每次读写都直接操作C侧原生变量内存,避免值拷贝导致的不同步问题:
- 首先定义对应属性的读写回调
// C侧全局偏好变量 int myPref = 0; // jsPref的读回调 static JSValue js_get_myPref(JSContext *ctx, JSValueConst this_val, int magic) { return JS_NewInt32(ctx, myPref); } // jsPref的写回调 static JSValue js_set_myPref(JSContext *ctx, JSValueConst this_val, JSValueConst val, int magic) { int32_t v; // 类型校验失败直接抛JS异常 if (JS_ToInt32(ctx, &v, val)) { return JS_EXCEPTION; } myPref = v; return JS_UNDEFINED; }
- 把属性注册到JS全局对象
// 上下文初始化完成后执行一次即可 JSValue global_obj = JS_GetGlobalObject(ctx); JS_DefinePropertyGetSet(ctx, global_obj, JS_NewAtom(ctx, "jsPref"), JS_NewCFunctionMagic(ctx, js_get_myPref, "get jsPref", 0, JS_CFUNC_generic_magic, 0), JS_NewCFunctionMagic(ctx, js_set_myPref, "set jsPref", 1, JS_CFUNC_generic_magic, 0), JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE | JS_PROP_ENUMERABLE); JS_FreeValue(ctx, global_obj);
此时在JS中执行jsPref = 1会直接修改C侧myPref的值,读jsPref也会获取C侧变量的最新值。
结构体变量暴露实现
思路是自定义JS类,类实例内部存储C侧结构体的指针,每个结构体成员对应绑定类的getter/setter属性:
- 先定义结构体和对应的JS类
// C侧结构体定义 typedef struct { int a; int b; } MyPrefStruct; // 全局配置实例 MyPrefStruct myPref = {0, 0}; // 类ID和析构函数定义 static JSClassID js_mypref_class_id; static void js_mypref_finalizer(JSRuntime *rt, JSValue val) { // 如果是动态分配的结构体,这里加释放逻辑,全局实例可留空 // MyPrefStruct *p = JS_GetOpaque(val, js_mypref_class_id); // free(p); } static const JSClassDef js_mypref_class_def = { .class_name = "MyPref", .finalizer = js_mypref_finalizer, };
- 定义结构体每个成员的读写回调
// 成员a的读回调 static JSValue js_mypref_get_a(JSContext *ctx, JSValueConst this_val, int magic) { MyPrefStruct *p = JS_GetOpaque(this_val, js_mypref_class_id); if (!p) return JS_EXCEPTION; return JS_NewInt32(ctx, p->a); } // 成员a的写回调 static JSValue js_mypref_set_a(JSContext *ctx, JSValueConst this_val, JSValueConst val, int magic) { MyPrefStruct *p = JS_GetOpaque(this_val, js_mypref_class_id); if (!p) return JS_EXCEPTION; int32_t v; if (JS_ToInt32(ctx, &v, val)) return JS_EXCEPTION; p->a = v; return JS_UNDEFINED; } // 成员b的读回调 static JSValue js_mypref_get_b(JSContext *ctx, JSValueConst this_val, int magic) { MyPrefStruct *p = JS_GetOpaque(this_val, js_mypref_class_id); if (!p) return JS_EXCEPTION; return JS_NewInt32(ctx, p->b); } // 成员b的写回调 static JSValue js_mypref_set_b(JSContext *ctx, JSValueConst this_val, JSValueConst val, int magic) { MyPrefStruct *p = JS_GetOpaque(this_val, js_mypref_class_id); if (!p) return JS_EXCEPTION; int32_t v; if (JS_ToInt32(ctx, &v, val)) return JS_EXCEPTION; p->b = v; return JS_UNDEFINED; }
- 绑定属性并注册全局实例
// 属性表定义 static const JSPropertyDef js_mypref_props[] = { { .name = "a", .getter = JS_NewCFunctionMagic(js_mypref_get_a, "get a", 0, JS_CFUNC_generic_magic, 0), .setter = JS_NewCFunctionMagic(js_mypref_set_a, "set a", 1, JS_CFUNC_generic_magic, 0), .flags = JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE | JS_PROP_ENUMERABLE, }, { .name = "b", .getter = JS_NewCFunctionMagic(js_mypref_get_b, "get b", 0, JS_CFUNC_generic_magic, 0), .setter = JS_NewCFunctionMagic(js_mypref_set_b, "set b", 1, JS_CFUNC_generic_magic, 0), .flags = JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE | JS_PROP_ENUMERABLE, }, {NULL} // 结束标记 }; // 运行时初始化时注册类,只执行一次 JS_NewClassID(&js_mypref_class_id); JS_NewClass(rt, js_mypref_class_id, &js_mypref_class_def); // 绑定属性到类原型 JSValue proto = JS_NewObject(ctx); JS_SetPropertyFunctionList(ctx, proto, js_mypref_props, sizeof(js_mypref_props)/sizeof(js_mypref_props[0]) - 1); JS_SetClassProto(ctx, js_mypref_class_id, proto); // 创建绑定C侧结构体的实例,注册到全局对象 JSValue global_obj = JS_GetGlobalObject(ctx); JSValue js_pref_inst = JS_NewObjectClass(ctx, js_mypref_class_id); JS_SetOpaque(js_pref_inst, &myPref); // 存储C侧结构体指针到JS对象 JS_SetPropertyStr(ctx, global_obj, "jsPref", js_pref_inst); JS_FreeValue(ctx, global_obj);
此时在JS中执行jsPref.a = 1;jsPref.b = 2会直接同步修改C侧myPref结构体的对应成员。如果有其他类型的结构体成员,仅需修改对应getter/setter里的类型转换逻辑即可,C++环境下把上述代码放在extern "C"块中即可直接编译使用。
内容的提问来源于stack exchange,提问作者moi
相关产品推荐
相关产品推荐

