I am interested in using QuickJS to modify preferences in a C++ app. So, there is a preference variable called
int myPref = 0
I want to expose this as a JS variable called
jsPref
Then the user can modify this directly via JS using
jsPref = 1
This can be extended to a struct - where one can define
struct myPref { int a, int b};
which is exposed as a global in JS as jsPref
Inside the JS script, I wish to call
jsPref.a = 1;
jsPref.b = 2;
and the assignments should be reflected in the C struct myPref
Sadly, I do not even have sample code on exposing a struct. After searching, I only came up with examples of C/C++ functions exposed as JS methods.
Take a look at e.g. JS_AddIntrinsicAtomics
in the QuickJS source code. It uses JS_SetPropertyFunctionList
to create an object whose properties are defined by a simple array of structures, with the help of some convenience macros.
Instead of JS_CFUNC_MAGIC_DEF
, you can use JS_CGETSET_DEF
or JS_CGETSET_MAGIC_DEF
to define custom properties with getters and setters. Since the contents of the JSContext
structure are not exposed in the public API, you will have to use JS_GetGlobalObject
to access the global object. Otherwise though, what you need to do is substantially the same.
This should look something like this:
// those will be called when jsPref.a is accessed
static JSValue pref_a_get(JSContext *ctx, JSValueConst this_val);
static JSValue pref_a_set(JSContext *ctx, JSValueConst this_val, JSValueConst value);
// those will be called when jsPref.b or jsPref.c is accessed and receive 123 or 456 respectively in the `magic` parameter
static JSValue pref_bc_get(JSContext *ctx, JSValueConst this_val, int magic);
static JSValue pref_bc_set(JSContext *ctx, JSValueConst this_val, JSValueConst value, int magic);
static const JSCFunctionListEntry prefs_obj_props[] = {
JS_CGETSET_DEF("a", pref_a_get, pref_a_set),
JS_CGETSET_MAGIC_DEF("b", pref_bc_get, pref_bc_set, 123),
JS_CGETSET_MAGIC_DEF("c", pref_bc_get, pref_bc_set, 456),
};
static const JSCFunctionListEntry prefs_obj[] = {
JS_OBJECT_DEF("jsPref", prefs_obj_props, countof(prefs_obj_props), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE ),
};
void create_prefs_obj(JSContext *ctx)
{
JSValue globalThis = JS_GetGlobalObject(ctx);
JS_SetPropertyFunctionList(ctx, globalThis, prefs_obj, countof(prefs_obj));
JS_FreeValue(ctx, globalThis);
}
The above defines the struct variation. For a single preference, you can put a JS_CGETSET_DEF
definition directly in the prefs_obj
, to define an accessor-based property of the global object.
Note that properties defined using JS_CGETSET_DEF
are configurable, which means user code can perform e.g. delete jsPrefs.a;
and lose access to the exposed property. If you want to prevent that, you will have to write your own helper macro so that you can set property flags yourself. You may also want to have a look at JS_PreventExtensions
, to catch user code attempting to write to a preference that doesn’t exist. If you want to use that, you may need to create the preferences object with JS_NewObject
, define its properties with JS_SetPropertyFunctionList
, seal it and add it to the global object manually, with JS_DefinePropertyValue
, instead of doing everything at once with just one JS_SetPropertyFunctionList
call:
void create_prefs_obj(JSContext *ctx)
{
JSValue globalThis = JS_GetGlobalObject(ctx);
// null prototype to avoid namespace clashes
JSValue prefsObj = JS_NewObjectProto(ctx, JS_NULL);
// define preference properties
JS_SetPropertyFunctionList(ctx, prefsObj, prefs_obj, countof(prefs_obj));
// catch user code writing to nonexistent properties
JS_PreventExtensions(ctx, prefsObj);
// expose the preferences object
JS_DefinePropertyValueStr(ctx, globalThis, "jsPref", prefsObj, PROP_CONFIGURABLE | PROP_WRITABLE);
JS_FreeValue(ctx, globalThis);
}