Duktape是一个可嵌入的Javascript引擎,主要关注便携性和精简及紧凑性。
Duktape很容易集成到C/C++项目: 添加duktape.c和duktape.h到您的build中,并使用Duktape API从C代码中,调用ECMAScript代码的功能,反之亦然。
Movian使用Duktape作为Javascript引擎。
Programming model
Create a Duktape heap (a garbage collection region) and an initial context (essentially a thread handle) in your program.
Heap and context
A Duktape heap is a single region for garbage collection. A heap is used to allocate storage for strings, Ecmascript objects, and other variable size, garbage collected data. Objects in the heap have an internal heap header which provides the necessary information for reference counting, mark-and-sweep garbage collection, object finalization, etc. Heap objects can reference each other, creating a reachability graph from a garbage collection perspective. For instance, the properties of an Ecmascript object reference both the keys and values of the object’s property set. You can have multiple heaps, but objects in different heaps cannot reference each other directly; you need to use serialization to pass values between heaps.
A Duktape context is an Ecmascript “thread of execution” which lives in a certain Duktape heap. A context is represented by a duk_context * in the Duktape API, and is associated with an internal Duktape coroutine (a form of a co-operative thread). Each context is also associated with an environment consisting of global objects; contexts may share the same global environment but can also have different environments. The context handle is given to almost every Duktape API call, and allows the caller to interact with the value stack of the Duktape coroutine: values can be inserted and queries, functions can be called, and so on.
A Duktape heap
duk_context * ec->ec_duk
ec->ec_duk = duk_create_heap(es_mem_alloc, es_mem_realloc, es_mem_free, ec, NULL);
duk_set_top(ec->ec_duk, 0);
if (do_gc) duk_gc(ec->ec_duk, 0);
duk_destroy_heap(ec->ec_duk);
ec->ec_duk = NULL;
A Duktape context
duk_context *ctx = ec->ec_duk;
duk_push_global_stash(ctx);
duk_push_object(ctx);
duk_put_prop_string(ctx, -2, "roots");
duk_push_global_object(ctx);
source code
object
int obj_idx = duk_push_object(ctx);
duk_push_int(ctx, 42);
duk_put_prop_string(ctx, obj_idx, "meaningOfLife");
/* object is now: { "meaningOfLife": 42 } */
duk_push_string(ctx, "v2.0");
duk_put_prop_string(ctx, obj_idx, "currentVersionString");
/* object is now: { "currentVersionString": "v2.0" } */
duk_pop(ctx); /* pop object */
function
const duk_function_list_entry my_module_funcs[] = {
{ "tweak", do_tweak, 0 /* no args */ },
{ "adjust", do_adjust, 3 /* 3 args */ },
{ "frobnicate", do_frobnicate, DUK_VARARGS /* variable args */ },
{ NULL, NULL, 0 }
};
/* Initialize an object with a set of function properties, and set it to
* global object 'MyModule'.
*/
duk_push_global_object(ctx);
duk_push_object(ctx); /* -> [ ... global obj ] */
duk_put_function_list(ctx, -1, my_module_funcs);
duk_put_prop_string(ctx, -2, "MyModule"); /* -> [ ... global ] */
duk_pop(ctx);
/*
* In Javascript
*/
MyModule.tweak();
MyModule.adjust(a, b, c);
duk_push_c_function()
duk_ret_t my_addtwo(duk_context *ctx) {
double a, b;
/* Here one can expect that duk_get_top(ctx) == 2, because nargs
* for duk_push_c_function() is 2.
*/
a = duk_get_number(ctx, 0);
b = duk_get_number(ctx, 1);
duk_push_number(ctx, a + b);
return 1; /* 1 = return value at top
* 0 = return 'undefined'
* <0 = throw error (use DUK_RET_xxx constants)
*/
}
void test(void) {
duk_idx_t func_idx;
func_idx = duk_push_c_function(ctx, my_addtwo, 2);
duk_push_int(ctx, 2);
duk_push_int(ctx, 3); /* -> [ ... func 2 3 ] */
duk_call(ctx, 2); /* -> [ ... res ] */
printf("2+3 is %ld\n", (long) duk_get_int(ctx, -1));
duk_pop(ctx);
}
duk_push_lstring
const char tmp1[5] = { 'f', '\0', '\0', 'x', 'y' };
const char tmp2[1] = { '\0' };
duk_push_lstring(ctx, tmp1, 5); /* push the string "f\x00\x00xy" */
duk_push_lstring(ctx, tmp2, 1); /* push the string "\x00" */
duk_push_lstring(ctx, tmp2, 0); /* push empty string */
duk_push_lstring(ctx, NULL, 0); /* push empty string */
duk_push_lstring(ctx, NULL, 10); /* push empty string */
duk_is_object
Returns 1 if value at idx is an object, otherwise returns 0. If idx is invalid, also returns 0.
duk_bool_t duk_is_object(duk_context *ctx, duk_idx_t idx)
duk_get_prop_string
int es_prop_to_int(duk_context *ctx, int obj_idx, const char *id, int def)
{
if (!duk_is_object(ctx, obj_idx)) return def;
duk_get_prop_string(ctx, obj_idx, id);
if (duk_is_number(ctx, -1)) def = duk_to_int(ctx, -1);
duk_pop(ctx);
return def;
}
rstr_t * es_prop_to_rstr(duk_context *ctx, int obj_idx, const char *id)
{
rstr_t *r = NULL;
if(!duk_is_object(ctx, obj_idx)) return NULL;
duk_get_prop_string(ctx, obj_idx, id);
const char *str = duk_get_string(ctx, -1);
if(str != NULL) r = rstr_alloc(str);
duk_pop(ctx);
return r;
}
duk_to_boolean
duk_bool_t duk_to_boolean(duk_context *ctx, duk_idx_t idx)
Replace the value at idx with an Ecmascript ToBoolean() coerced value. Returns 1 if the result of the coercion true, 0 otherwise. If idx is invalid, throws an error.
duk_dup
void es_dumpstack(duk_context *ctx)
{
int size = duk_get_top(ctx);
printf("STACKDUMP\n");
for(int i = -1; i > -1 - size; i--) {
duk_dup(ctx, i);
printf(" [%5d]: %s\n", i, duk_safe_to_string(ctx, -1));
duk_pop(ctx);
}
}
- duk_get_top Get current stack top
- duk_dup Push a duplicate of value at from_idx to the stack
- duk_pop Pop one element off the stack
上述函数将stack
中所有元素打印出来,并清空stack
Example
int ecmascript_plugin_load(const char *id, const char *url,
char *errbuf, size_t errlen,
int version, const char *manifest,
int flags)
{
char storage[PATH_MAX];
snprintf(storage, sizeof(storage), "%s/plugins/%s", gconf.persistent_path, id);
es_context_t *ec = es_context_create(id, flags | ECMASCRIPT_PLUGIN, url, storage);
//TRACE(TRACE_ERROR, "[load]", "[%s] %s [%s]\n", id, url, manifest);
es_context_begin(ec);
duk_context *ctx = ec->ec_duk;
duk_push_global_object(ctx);
int plugin_obj_idx = duk_push_object(ctx);
duk_push_string(ctx, id);
duk_put_prop_string(ctx, plugin_obj_idx, "id");
duk_push_string(ctx, url);
duk_put_prop_string(ctx, plugin_obj_idx, "url");
duk_push_string(ctx, manifest);
duk_put_prop_string(ctx, plugin_obj_idx, "manifest");
duk_push_int(ctx, version);
duk_put_prop_string(ctx, plugin_obj_idx, "apiversion");
//TRACE(TRACE_ERROR, "[ec_path]", "%s\n", ec->ec_path);
if(ec->ec_path)
{
duk_push_string(ctx, ec->ec_path);
duk_put_prop_string(ctx, plugin_obj_idx, "path");
}
duk_put_prop_string(ctx, -2, "Plugin");
duk_pop(ctx);
if(version == 1)
{
int64_t ts0 = arch_get_ts();
if(es_load_and_compile(ec, "dataroot://res/ecmascript/legacy/api-v1.js"))
goto bad;
int64_t ts1 = arch_get_ts();
if(duk_pcall(ctx, 0))
{
es_dump_err(ctx);
goto bad;
}
int64_t ts2 = arch_get_ts();
if(es_load_and_compile(ec, url))
{
duk_pop(ctx);
goto bad;
}
int64_t ts3 = arch_get_ts();
duk_swap_top(ctx, 0);
if(duk_pcall_method(ctx, 0))
es_dump_err(ctx);
int64_t ts4 = arch_get_ts();
es_debug(ec, "API v1 emulation: Compile:%dms Exec:%dms",
((int)(ts1 - ts0)) / 1000,
((int)(ts2 - ts1)) / 1000);
es_debug(ec, "Plugin main: Compile:%dms Exec:%dms",
((int)(ts3 - ts2)) / 1000,
((int)(ts4 - ts3)) / 1000);
}
else
{
es_exec(ec, url);
}
bad:
es_context_end(ec, 1);
es_context_release(ec);
return 0;
}
Javascript中相关代码
var plugin = {
createService: function(title, url, type, enabled, icon) {
return require('movian/service').create(title, url, type, enabled, icon);
},
createStore: function(name) {
return require('movian/store').create(name);
},
addURI: function(re, callback) {
var page = require('movian/page');
return new page.Route(re, callback);
},
addSearcher: function(title, icon, cb) {
var page = require('movian/page');
return new page.Searcher(title, icon,cb);
},
//get from c source code
path: Plugin.path,
getDescriptor: function() {
if(this.descriptor === undefined)
this.descriptor = JSON.parse(Plugin.manifest);
return this.descriptor;
},
getAuthCredentials: popup.getAuthCredentials,
addHTTPAuth: require('native/io').httpInspectorCreate,
copyFile: require('native/fs').copyfile,
selectView: misc.selectView,
createSettings: function(title, icon, description) {
var settings = require('movian/settings');
return new settings.globalSettings(Plugin.id, title, icon, description);
},
cachePut: function(stash, key, obj, maxage) {
misc.cachePut('plugin/' + Plugin.id + '/' + stash,
key, JSON.stringify(obj), maxage);
},
cacheGet: function(stash, key) {
var v = misc.cacheGet('plugin/' + Plugin.id + '/' + stash, key);
return v ? JSON.parse(v) : null;
},
config: {},
properties: prop.global.plugin[Plugin.id],
addItemHook: function(conf) {
require('movian/itemhook').create(conf);
},
addSubtitleProvider: function(fn) {
require('native/subtitle').addProvider(function(root, query, basescore, autosel) {
var req = Object.create(query);
req.addSubtitle = function(url, title, language, format,
source, score) {
require('native/subtitle').addItem(root, url, title, language, format, source,
basescore + score, autosel);
}
fn(req);
}, Plugin.id, Plugin.id);
}
};