Chinaunix首页 | 论坛 | 博客
  • 博客访问: 469746
  • 博文数量: 142
  • 博客积分: 4126
  • 博客等级: 上校
  • 技术积分: 1545
  • 用 户 组: 普通用户
  • 注册时间: 2008-02-22 10:03
文章分类

全部博文(142)

文章存档

2011年(8)

2010年(7)

2009年(64)

2008年(63)

我的朋友

分类: C/C++

2009-01-14 16:29:28

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

1、下载源文件以及编译
在 下面有一个文件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 < >
        $(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 \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 \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 \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去编译这个文件,然后再执行已经编译的文件。

阅读(3238) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~