您的位置:首页 > 其它

利用SpiderMonkey进行嵌入式开发——学习总结

2009-03-03 15:46 417 查看
利用SpiderMonkey进行嵌入式开发——学习总结

许峰 2007/07/30

最近在学习javascript引擎SpiderMonkey,学了一个星期了,终于有点眉目,现将学习经验记录下来,已被后用。
一下将逐步记录我学习的过程。

1、下载源文件以及编译
http://ftp.mozilla.org/pub/mozilla.org/js/ 下面有一个文件js-1.60.tar.gz, 这个就是SpiderMonkey的源代码
将源码下载后,解压缩。比如我下载到 /home/xufeng/work/jsEngine 下面,解压后就产生一个文件夹js,源文件全部在js/src中,进入js/src,进行编译(请参考README)。
以上所用的命令有:
$tar xvzf js-1.60.tar.gz
$cd js/src
$make -f Makefile.ref

2.运行一个实例程序
编译过后就会生成javascript引擎的静态库和动态库,分别是libjs.a和libjs.so,在js/src/Linux_All_DBG.OBJ/目录下面.进入这个目录就可以看见.如果要使用javascript引擎,只要在头文件中包含jsapi.h 同时在编译的时候链接这些库就可以了.
在Linux_ALL_DBG.OBJ目录下面还有一个可执行程序 js, 运行这个程序将产生类似shell的东西,可以在下面运行javascript代码.

3.利用javascript引擎进行嵌入式开发
先写一个最简单的程序,用来执行一段javascript代码(javascript代码也是很简单).这个程序也基本上展示了最主要的几个API.
程序如下:
/**
*程序 test1.c
*执行一段javascript代码
*/

#include "jsapi.h"
/* EXIT_FAILURE and EXIT_SUCCESS */
#include "stdlib.h"
/* strlen */
#include "string.h"

static void usage();

int main(int argc,const char* argv[])
{
/* pointer to our runtime object */
if(argc!=2){
usage();
exit(-1);
}
JSRuntime *runtime = NULL;
/* pointer to our context */
JSContext *context = NULL;
/* pointer to our global JavaScript object */
JSObject *global = NULL;

/* script to run (should return 100) */
const char *script = argv[1];
/* JavaScript value to store the result of the script */
jsval rval;

/* create new runtime, new context, global object */
if ((!(runtime = JS_NewRuntime(1024L * 1024L)))
|| (!(context = JS_NewContext(runtime, 8192)))
|| (!(global = JS_NewObject(context, NULL, NULL, NULL)))
)
return EXIT_FAILURE;
/* set global object of context and initialize standard ECMAScript
* objects (Math, Date, ...) within this global object scope */
if (!JS_InitStandardClasses(context, global))
return EXIT_FAILURE;

/* now we are ready to run our script */
if (!JS_EvaluateScript(context, global, script, strlen(script), "script", 1, &rval))
return EXIT_FAILURE;

printf("the script's result is /n%d/n",JSVAL_TO_INT(rval));

/* clean up */
JS_DestroyContext(context);
JS_DestroyRuntime(runtime);
JS_ShutDown();
return EXIT_SUCCESS;
}

void usage()
{
printf("example1 script_content/n");
printf("for example:./example1 /"var a=1;b=2;a+b/"/n");
}
/**
*程序结束
*/
以上程序写好以后就要编译了,编译的时候要注意增加一些编译参数。这里我写了一个简单的Makefile
########################################################################################################
ROSS_COMPILE_PREFIX =
CROSS_COMPILE_PATH =
CC = $(CROSS_COMPILE_PREFIX)gcc
CFLAGS = -DXP_UNIX -DJS_THREADSAFE -g -O0 -Wall -W -pedantic -std=c99 -I$(HOME)/work/jsEngine/js/src/
LDFLAGS = -L$(HOME)/work/jsEngine/js/src/Linux_All_DBG.OBJ/
LIBS = -ljs
DFLAGS = -g -DDEBUG
CLINK = -c -o
RM = rm -f
CFILES = test1.c

OBJ = $(patsubst %.c,%.o, $(CFILES))
EXECUTABLE = $(patsubst %.c,%, $(CFILES))
DEPENDENCY = $(patsubst %.c,%.d, $(CFILES))

all: $(DEPENDENCY) $(EXECUTABLE)

indent :
find -name '*.[ch]'|xargs indent -kr -cli4 -i4 -nut
doc:
doxygen
include $(DEPENDENCY)
%: %.o
$(CC) $< $(LIBS) -o $@ $(LDFLAGS)
%.o: %.c
$(CC) -c $< -o$@ $(CFLAGS)
%.d: %.c
$(CC) -MM $(CFLAGS) $< >$@.$$$$;/
sed 's,/($*/)/.o:,/1.o $@:,g' < $@.$$$$ > $@;/
$(RM) $@.$$$$;
clean:
$(RM) $(EXECUTABLE) *.o *.bak *.c~ *.h~ *.d
########################################################################################################

需要注意的是CFLAGS和DFLAGS,针对自己的路径要做修改。
-ljs -- 链接的时候增加libjs.so库
-DXP_UNIX -- 告诉编译器define XP_UNIX,使得javascript引擎知道操作系统是Unix或类似于Unix系统(这个参数是必需的,否则编译出错,我就在这里碰了跟头)
-O0 -- 不让编译器进行优化

有了以上步骤就可以进行编译运行了。初步的工作已经完成。

执行javascript代码的主要过程有:
1. 创建一个Runtime, API为 JS_NewRuntime()
2. 创建一个Context, API为 JS_NewContext()
3. 创建一个对象Object, API为 JS_NewObject()
4. 初始化全局对象 JS_InitStandardClasses()
5. 执行javascript代码 JS_EvaluateScript()
6. 销毁上下文Context JS_DestroyContext()
7. 销毁Runtime JS_DestroyRuntime()
以上就是主要的过程, 接下来的一些深入的工作都是基于此的.

4. 利用javascript引擎来解析一个javascript文件
在上面的程序中,javascript语句是当作命令行参数传给程序的, 有点的时候觉得不太方便, 于是将所有的javascript写道一个文件中, 然后解析这个文件, 执行语句. 执行一个文件中的javascript语句一般有两种方法:其一是定义FILE *fp, 利用 fp 将这个文件所有字节读出来,然后作为字符串传递给JS_EvaluateScript() API,得到结果;第二种是先编译这个js文件,然后再执行已经编译的文件。以下将给出这两种方法的实例。

方法一:
/**
*程序 test2.c
*利用FILE *fp文件指针执行js文件的代码
*/
#include "jsapi.h"

JSClass global_class =
{
"global",
0,
JS_PropertyStub,
JS_PropertyStub,
JS_PropertyStub,
JS_PropertyStub,
JS_EnumerateStub,
JS_ResolveStub,
JS_ConvertStub,
JS_FinalizeStub,
JSCLASS_NO_OPTIONAL_MEMBERS
};
int main()
{
JSString* jss;
char buf[5120];
int len;
jsval rval;
JSRuntime *rt;
JSContext *cx;
JSObject *globalObj;

rt = JS_NewRuntime(1024L*1024L);
if (!rt)return -1;
cx = JS_NewContext(rt, 8L*1024L);
if (!cx)return -1;

if (!(globalObj = JS_NewObject (cx, &global_class, NULL, NULL)))return -1;
JS_InitStandardClasses (cx, globalObj);

FILE* fp;
if (!(fp = fopen ("test.js", "r")))return -1;
len = fread (buf, 1, 5120, fp);
fclose (fp);
if (len <= 0)return -1;
JS_EvaluateScript (cx, globalObj, buf, len, "", 1, &rval);
jss = JS_ValueToString (cx, rval);
fprintf(stdout, "The result is: %s/n", JS_GetStringBytes(jss));
JS_DestroyContext(cx);
JS_DestroyRuntime(rt);
return 0;
}

/**
*程序结束
*/
javascript文件内容如下:
/**
*test.js
*/
var i = 1;
var j = 2;
i+j;
/**
*End
*/

好了,运行这个程序,将得到如下结果:
The result is: 3

方法二:
/**
*程序 test3.c
*先编译js文件,再执行文件
*/
/* EXIT_FAILURE and EXIT_SUCCESS */
#include "stdlib.h"
/* strlen */
#include "string.h"
/* get SpiderMonkey API declarations */
#include "sys/types.h"
#include "sys/stat.h"
#include "jsapi.h"

/*Function prototypes definition*/
JSBool JsFileExecute(JSContext *ctx, const char *file);
int usage(const char *progname);

JSClass JsGlobalObjectClass = {
"System",
0,
JS_PropertyStub,
JS_PropertyStub,
JS_PropertyStub,
JS_PropertyStub,
JS_EnumerateStub,
JS_ResolveStub,
JS_ConvertStub,
JS_FinalizeStub,
JSCLASS_NO_OPTIONAL_MEMBERS
};

int main(int argc, char **argv)
{
JSRuntime *rt = NULL;
JSContext *ctx = NULL;
JSObject *obj = NULL;
char *file = NULL;
if (argc < 2)
{
usage(argv[0]);
}
else
{
file = argv[1];
}

rt = JS_NewRuntime(1024L*1024L);
if(!rt)
{
printf("Runtime created failed./n");
return -1;
}
ctx = JS_NewContext(rt, 8L*1024L);
if(!ctx)
{
printf("Context created failed./n");
JS_DestroyRuntime(rt);
return -1;
}
obj = JS_NewObject(ctx, &JsGlobalObjectClass, NULL, NULL);
if(!obj)
{
printf("Object created failed./n");
JS_DestroyContext(ctx);
JS_DestroyRuntime(rt);
return -1;
}
JS_InitStandardClasses(ctx, obj);

if(JsFileExecute(ctx, file))
{
printf("File execute successfully./n");
}
else
{
printf("File execute failed./n");
}
return 0;
}

int usage(const char *progname)
{
printf("Usage: %s <file>/n", progname);
exit(-1);
}

JSBool JsFileExecute(JSContext *ctx, const char *file)
{
JSScript *script = NULL;
JSString *jss;
JSBool retval;
jsval ret;
JSObject *global = JS_GetGlobalObject(ctx);

script = JS_CompileFile(ctx, global, file);
if(script == NULL)
{
return JS_FALSE;
}
retval = JS_ExecuteScript(ctx, global, script, &ret);
jss = JS_ValueToString (ctx, ret);
printf("The result is: %s/n", JS_GetStringBytes(jss));
JS_DestroyScript(ctx, script);
return retval;
}
/**
*End
*/
这种方法先是用JS_CompileFile() API来编译文件,得到一个script,然后再JS_ExecuteScript() 来运行script,我觉得这种方法更加好一些。
运行的时候,js文件还是用的test.js,其源码同上。

5. 在程序中增加类
javascript是面向对象的,一个类常有构造函数、属性和方法等,在下面的程序中我们构建一个类,并逐步增加类的属性和方法。
在如下程序中,定义了一个PeopleClass类,然后用
static JSFunctionSpec PeopleMethods[] = {
{"print", PeoplePrint, 0, 0, 0},
{NULL}
};
来定义类的方法,如果有更多的方法也可以在这里定义,其中 "print" 是将在javascript中采用的方法名,PeoplePrint是在程序中实现的类的方法,后面的参数请参考API说明。
/**
*程序 test4.c
*/

#include "stdio.h"
#include "jsapi.h"

static JSBool PeoplePrint(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
JSBool JsFileExecute(JSContext *ctx, const char *file);
int usage(const char *progname);

typedef struct
{
char name[16];
char addr[64];
char tel[10];
}PeopleInfo;
static PeopleInfo pinfo={"xufeng", "Chongqing", "123456"};

JSClass global_class = {
"global",
0,
JS_PropertyStub,
JS_PropertyStub,
JS_PropertyStub,
JS_PropertyStub,
JS_EnumerateStub,
JS_ResolveStub,
JS_ConvertStub,
JS_FinalizeStub,
JSCLASS_NO_OPTIONAL_MEMBERS
};

static JSClass PeopleClass = {
"people",
0,
JS_PropertyStub,
JS_PropertyStub,
JS_PropertyStub,
JS_PropertyStub,
JS_EnumerateStub,
JS_ResolveStub,
JS_ConvertStub,
JS_FinalizeStub,
JSCLASS_NO_OPTIONAL_MEMBERS
};

static JSFunctionSpec PeopleMethods[] = {
{"print", PeoplePrint, 0, 0, 0},
{NULL}
};
int main(int argc, char **argv)
{
JSRuntime *rt;
JSContext *cx;
JSObject *globalObj,*PeopleObj;
char *file = NULL;
if (argc < 2)
{
usage(argv[0]);
}
else
{
file = argv[1];
}

rt = JS_NewRuntime(1024L*1024L);
if (!rt)return -1;
cx = JS_NewContext(rt, 8L*1024L);
if (!cx)return -1;

if (!(globalObj = JS_NewObject (cx, &global_class, NULL, NULL)))return -1;
JS_InitStandardClasses (cx, globalObj);

PeopleObj = JS_DefineObject (cx, globalObj, "people", &PeopleClass, 0, JSPROP_ENUMERATE);
JS_DefineFunctions (cx,PeopleObj, PeopleMethods);
if(JsFileExecute(cx, file))
{
printf("File execute successfully./n");
}
else
{
printf("File execute failed./n");
}
JS_DestroyContext(cx);
JS_DestroyRuntime(rt);
return 0;
}

static JSBool PeoplePrint(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
fprintf(stdout,"My Name is %s./nMy Addr is %s./nMy telephone is %s/n", pinfo.name, pinfo.addr, pinfo.tel);
return JS_TRUE;
}
JSBool JsFileExecute(JSContext *ctx, const char *file)
{
JSScript *script = NULL;
JSBool retval;
jsval ret;
JSObject *global = JS_GetGlobalObject(ctx);

script = JS_CompileFile(ctx, global, file);
if(script == NULL)
{
return JS_FALSE;
}
retval = JS_ExecuteScript(ctx, global, script, &ret);
JS_DestroyScript(ctx, script);
return retval;
}
int usage(const char *progname)
{
printf("Usage: %s <file>/n", progname);
exit(-1);
}
/**
*End
*/
运行的时候,在javascript中增加对象的使用,修改后的test.js内容如下:
/**
*javascript start
*/
people.print();
/**
*END
*/
编译后运行:./test4 test.js
程序将会产生如下结果:
My Name is xufeng.
My Addr is Chongqing.
My telephone is 123456
File execute successfully.

接下来我们增加类的属性,增加属性以及定义属性的Getter和Setter函数。
程序如下:
/**
*程序 test5.c
*增加对象的属性
*增加错误报警函数
*/
#include "stdio.h"
#include "stdlib.h"
#include "jsapi.h"
#include "sys/types.h"
#include "sys/stat.h"

enum PEOPLE {NAME, ADDRESS, TEL};
static JSBool GetPeopleProperty (JSContext *cx, JSObject *obj, jsval id, jsval *vp);
static JSBool SetPeopleProperty (JSContext *cx, JSObject *obj, jsval id, jsval *vp);
static JSBool PeoplePrint(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
static void JsErrorHandler(JSContext *ctx, const char *msg, JSErrorReport *er);
int usage(const char *progname);
JSBool JsFileExecute(JSContext *ctx, const char *file);

typedef struct
{
char name[16];
char addr[64];
char tel[10];
}PeopleInfo;
static PeopleInfo pinfo={"xufeng", "Chongqing", "123456"};

JSClass global_class = {
"global",
0,
JS_PropertyStub,
JS_PropertyStub,
JS_PropertyStub,
JS_PropertyStub,
JS_EnumerateStub,
JS_ResolveStub,
JS_ConvertStub,
JS_FinalizeStub,
JSCLASS_NO_OPTIONAL_MEMBERS
};

static JSClass PeopleClass = {
"people",
0,
JS_PropertyStub,
JS_PropertyStub,
GetPeopleProperty,
SetPeopleProperty,
JS_EnumerateStub,
JS_ResolveStub,
JS_ConvertStub,
JS_FinalizeStub,
JSCLASS_NO_OPTIONAL_MEMBERS
};

static JSPropertySpec PeopleProperties[] = {
{"name", NAME, JSPROP_ENUMERATE},
{"address", ADDRESS, JSPROP_ENUMERATE},
{"tel", TEL, JSPROP_ENUMERATE},
{NULL}
};
static JSFunctionSpec PeopleMethods[] = {
{"print", PeoplePrint, 0, 0, 0},
{NULL}
};
int main(int argc, char **argv)
{
JSRuntime *rt;
JSContext *cx;
JSObject *globalObj,*PeopleObj;
char *file = NULL;
if (argc < 2)
{
usage(argv[0]);
}
else
{
file = argv[1];
}
rt = JS_NewRuntime(1024L*1024L);
if (!rt)return -1;
cx = JS_NewContext(rt, 8L*1024L);
if (!cx)return -1;
JS_SetErrorReporter(cx, JsErrorHandler);

if (!(globalObj = JS_NewObject (cx, &global_class, NULL, NULL)))return -1;
JS_InitStandardClasses (cx, globalObj);

PeopleObj = JS_DefineObject (cx, globalObj, "people", &PeopleClass, 0, JSPROP_ENUMERATE);
JS_DefineProperties (cx,PeopleObj, PeopleProperties);
JS_DefineFunctions (cx,PeopleObj, PeopleMethods);
if(JsFileExecute(cx, file))
{
printf("File execute successfully./n");
}
else
{
printf("File execute failed./n");
}
JS_DestroyContext(cx);
JS_DestroyRuntime(rt);
return 0;
}

static void JsErrorHandler(JSContext *ctx, const char *msg, JSErrorReport *er)
{
printf("JS Error: %s/nFile: %s:%u/n", msg, er->filename, er->lineno);
}

static JSBool GetPeopleProperty (JSContext *cx, JSObject *obj, jsval id, jsval *vp)
{
if (JSVAL_IS_INT(id)) {
switch (JSVAL_TO_INT(id)) {
case NAME:
*vp=STRING_TO_JSVAL (JS_NewStringCopyZ (cx,pinfo.name));
break;
case ADDRESS:
*vp=STRING_TO_JSVAL (JS_NewStringCopyZ (cx,pinfo.addr));
break;
case TEL:
*vp=STRING_TO_JSVAL (JS_NewStringCopyZ (cx,pinfo.tel));
break;
}
}
return JS_TRUE;
}

static JSBool SetPeopleProperty (JSContext *cx, JSObject *obj, jsval id, jsval *vp)
{
if (JSVAL_IS_INT(id)) {
switch (JSVAL_TO_INT(id)) {
case NAME:
strncpy(pinfo.name, JS_GetStringBytes(JS_ValueToString(cx, *vp)), 15);
break;
case ADDRESS:
strncpy(pinfo.addr, JS_GetStringBytes(JS_ValueToString(cx, *vp)), 63);
break;
case TEL:
strncpy(pinfo.tel, JS_GetStringBytes(JS_ValueToString(cx, *vp)), 9);
break;
}
}
return JS_TRUE;
}

static JSBool PeoplePrint(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
fprintf(stdout,"My Name is %s./nMy Addr is %s./nMy telephone is %s/n", pinfo.name, pinfo.addr, pinfo.tel);
return JS_TRUE;
}

int usage(const char *progname)
{
printf("Usage: %s <file>/n", progname);
exit(-1);
}
JSBool JsFileExecute(JSContext *ctx, const char *file)
{
JSScript *script = NULL;
JSBool retval;
jsval ret;
JSObject *global = JS_GetGlobalObject(ctx);

script = JS_CompileFile(ctx, global, file);
if(script == NULL)
{
return JS_FALSE;
}
retval = JS_ExecuteScript(ctx, global, script, &ret);
JS_DestroyScript(ctx, script);
return retval;
}
/**
*END
*/
接下来修改test.js,修改后如下:
/**
*javascript start
*/
people.print();
people.name = "John";
people.address = "Beijing";
people.tel = "00099988";
people.print();
/**
*END
*/
编译运行 ./test5 test.js 得到如下结果:
My Name is xufeng.
My Addr is Chongqing.
My telephone is 123456
My Name is John.
My Addr is Beijing.
My telephone is 00099988
File execute successfully.

注意:在上面程序中增加了一个错误报警函数 JsErrorHandler(),然后 JS_SetErrorReporter(cx, JsErrorHandler);设置这个ErrorReporter,只要编译javascript出错了,这个函数就会运行,打印出出错的位置。

工作就做到了这里,希望对你有点用处。

附:

将ECMAScript值转换为本地值,一般采用以下函数:
JS_ValueToNumber, JS_ValueToInt32, JS_ValueToECMAInt32, JS_ValueToECMAUint32,
JS_ValueToBoolean, JS_ValueToUint16, JS_ValueToString, JS_ValueToObject,
JS_ValueToFunction

将本地值转化为ECMAScript值,一般采用以下函数:
JS_NewNumberValue, JS_NewJS_NewStringCopyZ, JS_UCStringCopyZ, JS_NewArrayObject
and macros from the ..._TO_JSVAL family: BOOLEAN_TO_JSVAL, STRING_TO_JSVAL,
OBJECT_TO_JSVAL

内存使用:
可以设置两种内存使用限制。通过JS_NewRuntime可以限制每个runtime堆的大小,通过JS_NewContext可以限制每个
context栈的大小。

利用spidermonkey来执行一个javascript文件有两种方法,第一种是将这个文件读出来然后作为字符串传递给spidermonkey,第二种是告诉spidermonkey去编译这个文件,然后再执行已经编译的文件。

参考:

https://developer.mozilla.org/En/SpiderMonkey/JSAPI_User_Guide
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐