MuJS官网示例讲解
原创炼气士 最后发布于2018-03-06 14:37:41 阅读数 736 收藏
展开
前提:已经在linux中安装好MuJS,MuJS安装比较简单,参考安装包中的readme文件
本章介绍的环境:vm+centos6.5 32bit
官网示例链接:http://dev.mujs.com/docs/examples.html
示例1
A stand-alone interpreter
interpreter.c
#include <stdio.h>
#include <mujs.h>
int main(int argc, char **argv)
{
char line[256];
int ret;
//js_State *J = js_newstate(NULL, NULL, 0);
js_State *J = js_newstate(NULL, NULL, JS_STRICT);
//printf("JS_STRICT:%d
", JS_STRICT);
while(fgets(line, sizeof(line), stdin))
{
ret=js_dostring(J, line);
//printf("dostring ret:%d
", ret);
}
js_freestate(J);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
标题:一个独立的解释器
功能:借助mujs的库用C实现的一个javascript脚本解释器;编译和运行由标准输入得到的js脚本命令,如果脚本有错误,则返回错误信息。
下面是编译操作,我这里加上了g选项用于gdb调试
执行结果如下
输入var a = 1; 回车系统执行成功,未报异常;输入b=2则返回异常,提示b变量未定义。在一般的javascript模式下定义变量不带var是可以的,但在strict严格模式下不带var是不允许的。
在mujs.h中找到JS_STRICT的宏定义,flag标记只有一个值
/* State constructor flags */
enum {
JS_STRICT = 1,
};
1
2
3
4
下面做个尝试,将flag设置为0 ,即执行代码
js_State *J = js_newstate(NULL, NULL, 0);
1
编译后,我们可以看到在终端中输入b=0时就没有上报异常了。
我们结合前一篇参考手册中js_dostring函数的说明来看
函数原型
int js_dostring(js_State *J, const char *source);
// J为js_State指针,与c进行交互的关键集合,需要先进行创建,
// source为javascript脚本,字符串格式。
// 如果发生错误,调用report上报异常,并返回1; 返回0表示成功。
1
2
3
4
所以放开interpreter.c中的注释,可以看到执行成功时ret的值为0,失败时为1。
下面我们重点看看js_State结构体,这个太重要了,javascript和c的互相作用就靠它了,在文件jsi.h中可以找到定义。
/* State struct */
struct js_State
{
void *actx;
void *uctx;
js_Alloc alloc;
js_Report report;
js_Panic panic;
js_StringNode *strings;
int default_strict;
int strict;
......
......
int nextref; /* for js_ref use */
js_Object *R; /* registry of hidden values */
js_Object *G; /* the global object */
js_Environment *E; /* current environment scope */
js_Environment *GE; /* global environment scope (at the root) */
/* execution stack */
int top, bot;
js_Value *stack;
/* garbage collector list */
int gcmark;
int gccounter;
js_Environment *gcenv;
js_Function *gcfun;
js_Object *gcobj;
js_String *gcstr;
/* environments on the call stack but currently not in scope */
int envtop;
js_Environment *envstack[JS_ENVLIMIT];
/* debug info stack trace */
int tracetop;
js_StackTrace trace[JS_ENVLIMIT];
/* exception stack */
int trytop;
js_Jumpbuf trybuf[JS_TRYLIMIT]; //异常跟踪 jump_buf类型,被setjmp调用。
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
结构体中的注释已经很清楚了,里面stack有:
1.execution stack
2.environments on the call stack but currently not in scope
3.debug info stack trace
4.exception stack
示例2
Hello, world!
hello.c
//Hello, world!
#include <stdio.h>
#include <mujs.h>
static void hello(js_State *J)
{
const char *name = js_tostring(J, 1);
printf("Hello, %s!
", name);
js_pushundefined(J);
}
int main(int argc, char **argv)
{
js_State *J = js_newstate(NULL, NULL, JS_STRICT);
js_newcfunction(J, hello, "hello", 1);
js_setglobal(J, "hello"); //设置为全局变量
js_dostring(J, "hello('world');"); // 调用javascript
js_freestate(J); // 释放J
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
执行结果为:
Hello world!
基本的逻辑如下图,hello函数是自定义的函数,用c语言实现,并封装为“js_CFunction”类型。javascript脚本通过js_newcfunction创建(注册)hello函数。然后c主程序调用js_dostring函数加载js脚本,在脚本中hello(‘world)’就是完成对hello函数的调用,带入的’world’为传入参数。
我们逐行代码解读,在进入到函数内部是,点到为止,不再深入。
首先看看hello函数
// 函数定义遵循js_CFunction的函数指针定义
// 在mujs中js_CFunction的定义为
// typedef void (*js_CFunction)(js_State *J);
static void hello(js_State *J)
{
// 从J->stack的1位置获取参数1,本例只有1个参数,且参数类型为
// string,所以调用js_tostring(J, 1)
const char *name = js_tostring(J, 1);
printf("Hello, %s!
", name);
js_pushundefined(J); // 无返回值,压入undefined补全
}
1
2
3
4
5
6
7
8
9
10
11
typedef void (*js_CFunction)(js_State *J);
/*
由定义可知,创建javascript脚本可调用的函数,返回类型void, 函数参数只有js_State *J。
*/
1
2
3
4
现在对程序进行修改,加深对J->stack的理解
#include <stdio.h>
#include <mujs.h>
static void hello(js_State *J)
{
const char *name = js_tostring(J, 1);
int num = js_tonumber(J, 2); // 获取第二个参数
printf("Hello, %s! num:%d
", name, num);
js_pushundefined(J);
}
int main(int argc, char **argv)
{
js_State *J = js_newstate(NULL, NULL, JS_STRICT);
js_newcfunction(J, hello, "hello", 2);// 2个参数
js_setglobal(J, "hello");
js_dostring(J, "hello('world',100);");
js_freestate(J);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
执行结果
Hello, world! num:100
再修改为简单一点的调用,这样便于我们理解参考手册中参数的放置顺序
printargs.c
#include <stdio.h>
#include <mujs.h>
static void printargs(js_State *J)
{
int this_value = js_tonumber(J, 0);
int arg1 = js_tonumber(J, 1);
int arg2 = js_tonumber(J, 2);
int negative_arg1 = js_tonumber(J, -1);
int negative_arg2 = js_tonumber(J, -2);
int negative_this_value = js_tonumber(J, -3);
printf("this value:%x
", this_value);
printf("arg1:%d
", arg1);
printf("arg2:%d
", arg2);
printf("negative arg1:%d
", negative_arg1);
printf("negative arg2:%d
", negative_arg2);
printf("negative this value:%x
", negative_this_value);
}
int main(void)
{
js_State *J = js_newstate(NULL, NULL, JS_STRICT);
js_newcfunction(J, printargs, "printargs", 2);
js_setglobal(J, "printargs");
js_dostring(J, "printargs(1,2);");
js_freestate(J);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
编译后执行结果
[root@bogon javascript]# gcc printargs.c -lmujs -g -o printargs
[root@bogon javascript]# ./printargs
this value:80000000
arg1:1
arg2:2
negative arg1:2
negative arg2:1
negative this value:8000000
js_State->stack中的参数和this value排序如图,正序从下往上0,1,2;逆序从上往下,-1,-2,-3。this value的值为0x80000000,属于无效值。
示例3
Configuration file
从config.js脚本中加载全局变量
config.js,包含全局变量foo coo 和 局部变量boo
var foo=2;
var coo = "string"
function afunc()
{
var boo = 100;
}
1
2
3
4
5
6
config_file.c
#include <stdio.h>
#include <mujs.h>
int main(void)
{
js_State *J = js_newstate(NULL, NULL, JS_STRICT);
js_dofile(J, "config.js"); // 加载js文件
js_getglobal(J, "foo"); // 获取js中的全局变量foo
int foo = js_tonumber(J, -1);
printf("foo: %d
", foo);
js_pop(J,1); // 弹出栈顶1个元素,即foo
foo = 0;
// js_getglobal(J, "foo"); // 如放开注释,foo可以再次获取
foo = js_tonumber(J, -1); // 此时再获取foo,则已经没有了
printf("foo: %d
", foo);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
编译后执行结果
[root@bogon javascript]# ./config_file
foo: 2
foo: -2147483648
1
2
3
修改一下例子,再看看
config_file_ex.c
#include <stdio.h>
#include <mujs.h>
int main(void)
{
js_State *J = js_newstate(NULL, NULL, JS_STRICT);
js_dofile(J, "config.js");
js_getglobal(J, "foo");
js_getglobal(J, "coo");
js_getglobal(J, "boo");
int foo = js_tonumber(J, -3);
char *coo = js_tostring(J, -2);
int boo = js_tonumber(J, -1);
printf("foo: %d
", foo);
printf("coo: %s
", coo);
printf("boo: %d
", boo);
js_pop(J,1);
foo = 0;
foo = js_tonumber(J, -2);
coo = NULL;
coo = js_tostring(J, -1);
printf("foo: %d
", foo);
printf("coo: %s
", coo);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
执行结果
[root@bogon javascript]# ./config_file_ex1
foo: 2
coo: string
boo: -2147483648
foo: 2
coo: string
1
2
3
4
5
6
局部变量boo无法获取,在执行js_getglobal(J, “boo”)时,也会压入一个空值undefined。
再看一个例子
config_file_ex2.c
#include <stdio.h>
#include <mujs.h>
int main(void)
{
js_State *J = js_newstate(NULL, NULL, JS_STRICT);
js_dofile(J, "config.js");
js_getglobal(J, "coo"); /* js全局变量 */
js_getglobal(J, "foo"); /* js全局变量 */
js_getglobal(J, "doo"); /* 无效 */
js_getglobal(J, "eoo"); /* 无效 */
char *coo = js_tostring(J, -4);
int foo = js_tonumber(J, -3);
int doo = js_tonumber(J, -2);
int eoo = js_tonumber(J, -1);
printf("foo: %d
", foo);
printf("coo: %s
", coo);
printf("doo: %d
", doo);
printf("eoo: %d
", eoo);
js_pop(J,2);
coo = js_tostring(J, -2);
foo = js_tonumber(J, -1);
printf("*******poped*******
");
printf("foo: %d
", foo);
printf("coo: %s
", coo);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
执行结果
[root@bogon javascript]# ./config_file_ex2
foo: 2
coo: string
doo: -2147483648
eoo: -2147483648
*******poped*******
foo: 2
coo: string
1
2
3
4
5
6
7
8
doo和eoo没有,所以获取失败,但也会压栈占用堆栈空间;栈顶排序和js脚本中全局变量的位置无关,只与压入堆栈的顺序有关;这种全局变量的获取,应该也包含函数变量,这个目前只是猜测,后面用例子来验证。
示例4
Object manipulation
源码
// t = { foo: 42, bar: true }
js_newobject(J);
{
js_pushnumber(J, 42);
js_setproperty(J, -2, "foo");
js_pushboolean(J, 1);
js_setproperty(J, -2, "bar");
}
js_setglobal(J, "t");
1
2
3
4
5
6
7
8
9
10
扩展为可执行程序,代码如下
object1.c
#include <stdio.h>
#include <mujs.h>
struct T
{
int foo;
unsigned char bar;
};
int main(void)
{
js_State *J = js_newstate(NULL, NULL, JS_STRICT);
// t = {foo:42, bar:12};
js_newobject(J);
{
js_pushnumber(J, 42);
js_setproperty(J, -2, "foo");
js_pushnumber(J, 12);
js_setproperty(J, -2, "bar");
}
js_setglobal(J, "t");
struct T t;
js_getglobal(J, "t"); // 找到t并将object压栈
js_getproperty(J, -1, "foo"); // 此时t在栈顶,得到foo后再将foo的值压栈
js_getproperty(J, -2, "bar"); // 此时t已经下沉一次,所以idx为-2
t.foo = js_tonumber(J, -2);
t.bar = js_tonumber(J, -1);
printf("t.foo:%d, t.bar:%d
", t.foo, t.bar);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
执行结果
[root@bogon javascript]# gcc object1.c -lmujs -g -o object1
[root@bogon javascript]# ./object1
t.foo:42, t.bar:12
1
2
3
这里面难点是在于理解js_setproperty中的idx参数,例子中都是”-2”,这个值是如何确定的,官方的参考手册too simple了,根本没有解释,只能去啃源码。
void js_setproperty(js_State *J, int idx, const char *name);
下面用堆栈图来说明object的创建和引用步骤
我们可以通过一个例子来验证,在创建对象时增加属性,经过了以下几个步骤:
1、创建object,将object压入stack;
2、压入属性,可能是number, string, boolean等等
3、将属性与object关联(加入到object的property chain),丢弃该属性
4、如果要继续添加属性,重复2,3步骤
object2.c
#include <stdio.h>
#include <mujs.h>
struct T
{
int foo;
unsigned char bar;
};
int main(void)
{
js_State *J = js_newstate(NULL, NULL, JS_STRICT);
js_dofile(J, "object.js");
int i;
js_pushnumber(J, 100);
// t = {foo:42, bar:12};
js_newobject(J);
{
i = js_tonumber(J, -2);
printf("i:%d
", i);
js_pushnumber(J, 42);
js_setproperty(J, -2, "foo");
i = js_tonumber(J, -2);
printf("i:%d
", i);
js_pushnumber(J, 12);
js_setproperty(J, -2, "bar");
}
js_setglobal(J, "t");
i = js_tonumber(J, -1);
printf("i:%d
", i);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
执行结果
[root@bogon javascript]# gcc object2.c -lmujs -g -o object2
[root@bogon javascript]# ./object2
i:100
i:100
i:100
1
2
3
4
5
三个地方打印的i都是100,都获取到了number的值,所以大家可以自己对照着看
示例5
Callbacks from C to JS (by name)
static int call_callback(js_State *J, const char *arg1, int arg2)
{
int result;
/* Find the function to call. */
js_getglobal(J, "my_callback");
/* Push arguments to function. */
js_pushnull(J); /* the 'this' object to use */
js_pushstring(J, arg1);
js_pushnumber(J, arg2);
/* Call function and check for exceptions. */
if (js_pcall(J, 2)) {
fprintf(stderr, "an exception occurred in the javascript callback
");
js_pop(J, 1);
return -1;
}
/* Retrieve return value. */
result = js_tonumber(J, -1);
js_pop(J, 1);
return result;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
扩展为可执行程序
callback1.c
#include <stdio.h>
#include <mujs.h>
static void jsB_print(js_State *J)
{
int i, top = js_gettop(J);
for (i = 1; i < top; ++i) {
const char *s = js_tostring(J, i);
if (i > 1) putchar(' ');
fputs(s, stdout);
}
putchar('
');
js_pushundefined(J);
}
static int call_callback(js_State *J, const char *arg1, int arg2)
{
int result;
/* Find the function to call. */
js_getglobal(J, "my_callback");
/* Push arguments to function. */
js_pushnull(J); /* the 'this' object to use */
js_pushstring(J, arg1);
js_pushnumber(J, arg2);
/* Call function and check for exceptions. */
if (js_pcall(J, 2)) {
fprintf(stderr, "an exception occurred in the javascript callback
");
js_pop(J, 1);
return -1;
}
/* Retrieve return value. */
result = js_tonumber(J, -1);
js_pop(J, 1);
return result;
}
void main(void)
{
js_State * J = js_newstate(NULL, NULL, JS_STRICT);
js_dofile(J, "callback.js");
js_newcfunction(J, jsB_print, "print", 0);
js_setglobal(J, "print");
int ret = call_callback(J, "Hello world!", 100);
printf("ret:%d
", ret);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
对应的callback.js脚本也很简单
function my_callback(arg1, arg2)
{
print(arg1);
return (arg2+2);
}
1
2
3
4
5
6
执行结果
[root@bogon javascript]# gcc callback1.c -g -lmujs -o callback1
[root@bogon javascript]# ./callback1
Hello world!
ret:102
1
2
3
4
源码简单,注释也相对完整,功能不多说,还是画一下堆栈图,这样就比较清楚,在这之前,还是用之前压入一个number值来测试不同阶段number值的位置来看看,整个过程做了哪些操作。
callback2.c
#include <stdio.h>
#include <mujs.h>
static void jsB_print(js_State *J)
{
int i, top = js_gettop(J);
for (i = 1; i < top; ++i) {
const char *s = js_tostring(J, i);
if (i > 1) putchar(' ');
fputs(s, stdout);
}
putchar('
');
js_pushundefined(J);
}
static int call_callback(js_State *J, const char *arg1, int arg2)
{
int result;
int i;
js_pushnumber(J, 100);
/* Find the function to call. */
js_getglobal(J, "my_callback");
i = js_tonumber(J, -2); // 1
printf("i:%d
", i);
/* Push arguments to function. */
js_pushnull(J); /* the 'this' object to use */
js_pushstring(J, arg1);
js_pushnumber(J, arg2);
i = js_tonumber(J, -5); // 2
printf("i:%d
", i);
/* Call function and check for exceptions. */
if (js_pcall(J, 2)) {
fprintf(stderr, "an exception occurred in the javascript callback
");
js_pop(J, 1);
return -1;
}
i = js_tonumber(J, -2); // 3
printf("i:%d
", i);
/* Retrieve return value. */
result = js_tonumber(J, -1);
js_pop(J, 1);
i = js_tonumber(J, -1); // 4
printf("i:%d
", i);
return result;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
执行结果
[root@bogon javascript]# ./callback2
i:100
i:100
Hello world!
i:100
i:100
ret:102
1
2
3
4
5
6
7
说明:
1. 函数调用都是围绕着stack的压栈和出栈的操作,在函数callback之后,要清空使用记录,恢复到调用前的状态。
2. 例子中还借用了mujs开源包main.c中的代码,实现了js脚本的print功能,支持终端输出。我们知道javascript主要是web开发,所以在标准输入输出,文本操作方面支持的不好,mujs考虑到这点,在main.c举例增加了相关函数,使得javascript可以调用这些功能函数,但这样一来javascript就不是标准的版本,必须在mujs解释器下执行,如果更换其他解释器,那么这些函数需要调整。
示例6
Callbacks from C to JS
const char *handle = NULL; /* handle to stowed away js function */
static void set_callback(js_State *J)
{
if (handle)
js_unref(J, handle); /* delete old function */
js_copy(J, 1);
handle = js_ref(J); /* stow the js function in the registry */
}
static void call_callback(js_State *J, int arg1, int arg2)
{
js_getregistry(J, handle); /* retrieve the js function from the registry */
js_pushnull(J);
js_pushnumber(J, arg1);
js_pushnumber(J, arg2);
js_pcall(J, 2);
js_pop(J, 1);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
这个例子没有看懂,后续解决。
示例7
Complete userdata example
源码比较长
#include <stdio.h>
#include <mujs.h>
#define TAG "File"
static void new_File(js_State *J)
{
FILE *file;
if (js_isundefined(J, 1)) {
file = stdin;
} else {
const char *filename = js_tostring(J, 1);
file = fopen(filename, "r");
if (!file)
js_error(J, "cannot open file: '%s'", filename);
}
js_currentfunction(J);
js_getproperty(J, -1, "prototype");
js_newuserdata(J, TAG, file);
}
static void File_prototype_readByte(js_State *J)
{
FILE *file = js_touserdata(J, 0, TAG);
js_pushnumber(J, getc(file));
}
static void File_prototype_readLine(js_State *J)
{
char line[256], *s;
FILE *file = js_touserdata(J, 0, TAG);
s = fgets(line, sizeof line, file);
if (s)
js_pushstring(J, line);
else
js_pushnull(J);
}
static void File_prototype_close(js_State *J)
{
FILE *file = js_touserdata(J, 0, TAG);
fclose(file);
js_pushundefined(J);
}
void initfile(js_State *J)
{
js_getglobal(J, "Object");
js_getproperty(J, -1, "prototype"); // File.prototype.[[Prototype]] = Object.prototype
js_newuserdata(J, TAG, stdin); // File.prototype.[[Userdata]] = stdin
{
js_newcfunction(J, File_prototype_readByte, "File.prototype.readByte", 0);
js_defproperty(J, -2, "readByte", JS_DONTENUM);
js_newcfunction(J, File_prototype_readLine, "File.prototype.readLine", 0);
js_defproperty(J, -2, "readLine", JS_DONTENUM);
js_newcfunction(J, File_prototype_close, "File.prototype.close", 0);
js_defproperty(J, -2, "close", JS_DONTENUM);
}
js_newcconstructor(J, new_File, new_File, "File", 1);
js_defglobal(J, "File", JS_DONTENUM);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
待续….
这个例子,也没看懂~
————————————————
版权声明:本文为CSDN博主「炼气士」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/ljd680/article/details/79423284