尝试MuJS
目录
前言
正文
安装
运行
看看官网案例——Hello, world!
看看案例中使用的函数的含义
js_newstate
js_newcfunction
js_setglobal
js_dostring
总体流程
看看官网案例——Configuration file
看看函数含义
js_dofile
js_getglobal就不必细说了
js_tonumber
读取前面的index.js
自定义console
下一个案例——Object manipulation
js_setproperty函数
前言
看到油管上一位大佬使用MuJS,简单来试试,学习一下。
MuJS is a lightweight Javascript interpreter designed for embedding in other software to extend them with scripting capabilities.
视频网址如下,如果能访问到。
https://www.youtube.com/watch?v=4kuxeEnFVYw&list=PLpM-Dvs8t0VYMyfbX11yAd7VAHPILM0kvhttps://www.youtube.com/watch?v=4kuxeEnFVYw&list=PLpM-Dvs8t0VYMyfbX11yAd7VAHPILM0kv
MuJS下载网址。
Index of /downloadshttps://mujs.com/downloads/
MuJShttps://mujs.com/
对于环境,笔者其实很少使用linux,但是笔者windows中有wsl,就在wsl中使用
正文
安装
下载mujs-1.3.7.tar.gz,即
wget https://mujs.com/downloads/mujs-1.3.7.tar.gz
解压
tar -xzvf mujs-1.3.7.tar.gz
进入mujs-1.3.7目录,使用make构建
make
如果构建失败,很可能是网络问题。
在build/debug目录下,生成了三个文件
libmujs.o mujs mujs-pp
因为MuJS 只实现ECMA-262,直白的说,mujs可以看成一个ES5引擎。没有let之类的的东西。
运行
在mujs-1.3.7目录下新建一个test目录,然后在其中创建一个js文件
vim index.js
内容如下
let a=1;
let b=2;
console.log(a+b);
运行,结果如下
/mujs-1.3.7/test$ ../build/debug/mujs ./index.js
SyntaxError: ./index.js:1: unexpected token: (identifier) (expected ';')
报错。
修改文件内容
var a=1;
var b=2;
console.log(a+b);
没问题
/mujs-1.3.7/test$ ../build/debug/mujs ./index.js
3
看看官网案例——Hello, world!
在test目录下新建一个test.c文件,其中内容如下
#include <stdio.h>
#include <mujs.h>static void hello(js_State *J)
{const char *name = js_tostring(J, 1);printf("Hello, %s!\n", 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');");js_freestate(J);
}
这是官网的案例,具体含义先不慌,该如何运行这个test.c文件,
可以使用gcc编译。同时考虑到位置是在test目录下,结合前面的内容,因此,编译命令如下
gcc -I.. test.c ../build/debug/libmujs.o -o test -lm
片段 | 含义 |
---|---|
gcc | 调用 GNU 编译器集合。 |
-I.. | 把上一级目录(.. )加入头文件搜索路径,使 #include <mujs.h> 能找到。 |
test.c | 要编译的源文件。 |
../build/debug/libmujs.o | 已经编译好的 MuJS 目标文件,里面包含了 js_newstate 、js_dostring 等函数的实现。 |
-o test | 指定输出可执行文件名叫 test 。 |
-lm | 链接数学库。 |
结果如下
/mujs-1.3.7/test$ gcc -I.. test.c ../build/debug/libmujs.o -o test -lm
/mujs-1.3.7/test$ ./test
Hello, world!
打印了hello world,没问题。
看看案例中使用的函数的含义
js_newstate
首先,先在mujs-1.3.7目录下搜索一下关于这个函数的定义
grep -rn js_newst
...
mujs.h:52:js_State *js_newstate(js_Alloc alloc, void *actx, int flags);
...
可以发现其中的定义在mujs.h中,且在52行
进去看看,可以发现一些东西
js_State * js_newstate(js_Alloc alloc, void *context, int flags)
/* State constructor flags */
enum {
JS_STRICT = 1,
};
根据英文意思,大致可以猜测参数的意思。
参数 | 说明 |
---|---|
alloc | 内存分配器 |
context | 给分配器携带的用户数据 |
flags | ES5 的模式。 |
总之,说白了,这就是一个new,初始化
js_newcfunction
位置如下
mujs.h:175:void js_newcfunction(js_State *J, js_CFunction fun, const char *name, int length);
参数意思如下
参数 | 含义 |
---|---|
J | 解释器实例。 |
fun | C 函数指针,必须符合签名 void fun(js_State *J) 。 |
name | 函数名。 |
length | 形参个数,传 0 表示“可变参数”,1 就告诉 MuJS 这个函数期望 1 个实参。 |
说白了,“C 函数 → JS 函数”
js_setglobal
mujs.h:140:void js_setglobal(js_State *J, const char *name);
参数意思如下
参数 | 含义 |
---|---|
J | 解释器实例。 |
name | 全局变量名。 |
说白了,将注册的函数变成全局的
js_dostring
mujs.h:60:int js_dostring(js_State *J, const char *source);
参数 | 含义 |
---|---|
J | 解释器实例。 |
source | JS 源码字符串。 |
说白了,执行js
总体流程
了解了几个函数的意思,大致明白代码在干什么了。
首先,自定义一个c语言函数
然后,变成js函数,
接着,注册成全局函数,
最后,执行。
哈哈哈哈,这个过程有点熟悉,和Webassembly类似。当然,二者还是不一样。
项目 | 本质 | 常见用途 |
---|---|---|
MuJS | 一个 轻量级 JavaScript 解释器(C 库)。 | 在 C/C++、嵌入式、桌面、服务端 脚本化,类似 Lua。 |
WebAssembly | 一种 低级字节码格式(不是语言)。 | 让 C/C++/Rust 等代码以接近原生速度在 浏览器 或 Node.js 里运行。 |
看看官网案例——Configuration file
js_dofile(J, "config.js")js_getglobal(J, "foo");
foo = js_tonumber(J, -1);
js_pop(J, 1);
从名字中,可以大致明白,应该是读取js文件,获取全局,转化成c变量,弹出栈顶。
表示疑惑???修改一些东西,运行一下再说
在test目录下新建file.c和config.js文件。
其中file.c的内容如下
#include<stdio.h>
#include<mujs.h>
int main(void){js_State *J = js_newstate(NULL, NULL, 0);int res=js_dofile(J, "config.js");printf("res = %d\n",res);js_getglobal(J, "foo");double foo = js_tonumber(J, -1);js_pop(J, 1);printf("foo = %.0f\n", foo);
}
其中config.js的内容如下
var foo=10;
编译并运行
/mujs-1.3.7/test$ gcc -I.. file.c ../build/debug/libmujs.o -o file -lm
/mujs-1.3.7/test$ ./file
res = 0
foo = 10
没问题。
看看函数含义
js_dofile
mujs.h:61:int js_dofile(js_State *J, const char *filename);
参数 | 说明 |
---|---|
J | 解释器实例。 |
filename | 待执行的 .js 文件路径。 |
这个js_dofile包含“读文件 → 编译 → 执行” 。
返回值是int,说明可以知道执行的结果,从上面看来,0是成功了,1就是失败的
js_getglobal就不必细说了
js_tonumber
mujs.h:205:double js_tonumber(js_State *J, int idx);
具体含义
参数 | 含义 |
---|---|
J | 解释器实例。 |
idx | 栈索引(正数从底往上,负数从顶往下;-1 就是栈顶)。 |
意思是把栈里idx位置的 JavaScript 值读出来,转成 C 。
还有js_pop,应该是弹出栈顶的意思,1代表个数。
mujs.h:221:void js_pop(js_State *J, int n);
读取前面的index.js
前面创建一个index.js,修改一下其中的内容,如下
var a=1;
var b=2;
console.log("====================")
console.log(a+b);
console.log("====================")
读取这个文件,看看打印结果,新建一个index.c文件
#include<stdio.h>
#include<mujs.h>
int main(void)
{js_State *J = js_newstate(NULL, NULL, 0);int res=js_dofile(J, "index.js");printf("res = %d\n",res);
}
结果如下
mujs-1.3.7/test$ gcc -I.. index.c ../build/debug/libmujs.o -o index -lm
mujs-1.3.7/test$ ./index
ReferenceError: 'console' is not defined
res = 1
没有定义console!!!!!!!!!!!!!!!!!!!!!!!!
自定义console
在最开始的那位油管上的大佬——使用的js_dostring和js_newcfunction自定义了console
首先,先搜一下console,然后就可以发现这样一个东西
main.c:233:static const char *console_js =
main.c:234: "var console = { log: print, debug: print, warn: print, error: print };"
main.c:348: js_dostring(J, console_js);
什么意思?
使用js_dostring定义了console,log对应于print这个函数
所以,只需要自定义print函数就可以了,即,使用前面的js_newcfunction。
而print函数也是可以搜索到的
grep -rn print main.c
.....
118:static void jsB_print(js_State *J)
.....
在main.c的118行,这个jsB_print就是注册的print。
因此,直接最终的代码,
#include<stdio.h>
#include<mujs.h>
static const char *console_js ="var console = { log: print, debug: print, warn: print, error: print };";
static void jsB_print(js_State *J)
{int i, n = js_gettop(J);for (i = 1; i < n; ++i) {const char *s = js_tostring(J, i);if (i > 1) putchar(' ');fputs(s, stdout);}putchar('\n');js_pushundefined(J);
}
int main(void)
{js_State *J = js_newstate(NULL, NULL, 0);js_newcfunction(J,jsB_print,"print",0);js_setglobal(J,"print");js_dostring(J,console_js);int res=js_dofile(J, "index.js");printf("res = %d\n",res);
}
编译并运行,结果如下
/mujs-1.3.7/test$ gcc -I.. index.c ../build/debug/libmujs.o -o index -lm
/mujs-1.3.7/test$ ./index
====================
3
====================
res = 0
没问题
下一个案例——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");
意思是很明显的,初始化一个对象,挂到全局。
运行一下,新建一个obj.c文件,结果如下
#include <stdio.h>
#include <mujs.h>
static void jsB_print(js_State *J)
{int i, n = js_gettop(J);for (i = 1; i < n; ++i) {const char *s = js_tostring(J, i);if (i > 1) putchar(' ');fputs(s, stdout);}putchar('\n');js_pushundefined(J);
}
int main(void)
{js_State *J = js_newstate(NULL, NULL, JS_STRICT);js_newcfunction(J,jsB_print,"print",0);js_setglobal(J,"print");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");js_dostring(J, "print('inline:'); print(JSON.stringify(t));");js_freestate(J);return 0;
}
js_setproperty函数
mujs.h:146:void js_setproperty(js_State *J, int idx, const char *name);
把栈顶值写到指定对象的指定属性,然后弹出栈顶
参数 | 含义 |
---|---|
J | 解释器实例。 |
idx | 目标对象在栈中的索引(负数从顶往下数)。 |
name | 要写入的属性名。 |
为什么是-2???
因为先把对象压栈,然后把属性压栈,属性就在栈顶,属性是-1,对象是-2,
编译并运行,结果如下
mujs-1.3.7/test$ ./obj
inline:
{"bar":true,"foo":42}
没问题。
行,就到这儿吧,有点意思。