zoukankan      html  css  js  c++  java
  • SpiderMonkey-让你的C++程序支持JavaScript脚本

    译序

    有些网友对为什么D2JSP能执行JavaScript脚本程序感到奇怪,因此我翻译了这篇文章,原文在这里。这篇教程手把手教你怎样利用SpiderMonkey创建一个能执行JavaScript脚本的C++程序,并让JavaScript脚本操纵你的C++程序的内部数据、操作。从这篇教程能够看到在SpiderMonkey引擎的帮助下,让C++程序支持JavaScript脚本是一件非常easy的事,更棒的是SpiderMonkey也能够在Macintosh和Unix平台使用。
    SpiderMonkey是GeckoFirefox浏览器的内核)的JavaScript脚本引擎,具体文档请看这里

    下面为翻译内容。

    ------------------------------------------------

    本教程的目的是教你怎样用JavaScript做为脚本语言使你的C++程序自己主动化。

    SpiderMonkey

    SpiderMonkey是Mozilla项目的一部分,用C语言写成,是负责运行JavaScript脚本的引擎。另外另一个叫Rhino的Java引擎。

    SpiderMonkey的最新版本号可在这里下载。它是以源码形式公布的,因此你必须自己编译它(译注:事实上网上有非常多编译好的二进制版本号,google一下js32.dll就可找到)。Visual C++用户能够在src文件夹下找到Workspace项目project文件来编译,编译结果会产生一个叫'js32.dll'的dll文件。

    SpiderMonkey也能够在Macintosh和Unix上使用,想了解怎样在这些平台上进行编译请阅读Readme.html。

    在C++中运行JavaScript程序

    步骤1-创建JavaScript runtime(执行时实例)

    初始化一个JavaScript runtime可用JS_NewRuntime方法,该方法将为runtime分配内存,同一时候还得指定一个字节数,当内存分配超过这个数字时垃圾收集器会自己主动执行。

    JSRuntime *rt = JS_NewRuntime(1000000L);
    if ( rt == NULL )
    {
        
    // Do some error reporting
    }

    步骤2-创建context(上下文环境)

    Context指明了脚本执行所需的栈大小,即分配给脚本执行栈的私有内存数量。每一个脚本都和它自己的context相关联。

    当一个context正在被某个脚本或线程使用时,其它脚本或线程不能使用该context。只是在脚本或线程结束时,该context能够被下一个脚本或线程重用。

    创建一个新context可用JS_NewContext方法。context必须关联到一个runtime,调用JS_NewContext方法时还必须指定栈的大小。

    JSContext *cx = JS_NewContext(m_rt, 8192);
    if ( cx == NULL )
    {
        
    // Do some error reporting
    }

    步骤3-初始化全局对象

    在一个脚本開始执行前,必须初始化一些大多数脚本会用到的通用的JavaScript函数和内置(build-in)类对象。

    全局对象是在一个JSClass结构中描写叙述的。该结构能够按下面方式初始化:

    JSClass globalClass =
    {
        
    "Global"0,
        JS_PropertyStub,  JS_PropertyStub,
        JS_PropertyStub, JS_PropertyStub,
        JS_EnumerateStub, JS_ResolveStub,
        JS_ConvertStub,  JS_FinalizeStub
    }
    ;
    如今创建和初始化这个全局对象:
    JSObject *globalObj = JS_NewObject(cx, &globalClass, 00);
    JS_InitStandardClasses(cx, globalObj);

    步骤4-运行脚本

    运行脚本的一种途径是使用JS_EvaluateScript方法:

    std::string script = "var today = Date(); today.toString();"
    jsval rval;
    uintN lineno 
    = 0;
    JSBool ok 
    = JS_EvaluateScript(cx, globalObj, script.c_str(), 
                                  script.length(), 
    "script", lineno, &rval);

    在这个脚本中,假设运行正确的话当天数据会保存在rval中。rval包括最后一个运行函数的结果。JS_EvaluteScript返回JS_TRUE代表运行成功,返回JS_FALSE则代表有发生错误。

    从rval得到对应的字符串值能够用以下的方法。在这里我不想解释全部细节,想获得更具体的信息请自己查API文档。

    JSString *str = JS_ValueToString(cx, rval);
    std::cout 
    << JS_GetStringBytes(str);

    步骤5-清理脚本引擎

    程序结束前必须对脚本引擎做一些清理工作:
    JS_DestroyContext(cx);
    JS_DestroyRuntime(rt);

    在C++中定义一个在JavaScript中用的类

    这个样例中用到的类定义例如以下:

    class Customer
    {
    public:
        
    int GetAge() return m_age; }
        
    void SetAge(int newAge) { m_age = newAge; }
        std::
    string GetName() return m_name; }
        
    void SetName(std::string newName) { m_name = newName; }

    private:
        
    int m_age;
        std::
    string m_name;
    }
    ;

    步骤1-JavaScript类

    从Customer类派生一个你想在JavaScript中用的新的C++类,或者创建一个包括一个Customer类型成员变量的新类。

    给JavaScript用的类得有一个JSClass结构,为此得创建一个JSClass类型的静态成员变量,该变量会被其它类用到,因此还得把它声明为public变量。别的类能够用该结构来推断对象的类型(见JS_InstanceOf API)。

    // JSCustomer.h
    class JSCustomer
    {
    public:
        JSCustomer() : m_pCustomer(NULL) 
        
    {
        }


        
    ~JSCustomer()
        
    {
            delete m_pCustomer;
            m_pCustomer 
    = NULL;
        }


        
    static JSClass customerClass;

    protected:
        
    void setCustomer(Customer *customer) 
        
    {
            m_pCustomer 
    = customer; 
        }


        Customer
    * getCustomer()
        
    {
            
    return m_pCustomer; 
        }


    private:
        Customer 
    *m_pCustomer;

    }
    ;

    该JSClass结构里包括了JavaScript类的名字、标志位以及给脚本引擎用的回调函数的名字。举个样例,脚本引擎使用回调函数从类中获取某个属性值。

    在C++类的实现文件里定义JSClass结构例如以下:

    // JSCustomer.cpp
    JSClass JSCustomer::customerClass = 
    {
        
    "Customer", JSCLASS_HAS_PRIVATE,
            JS_PropertyStub, JS_PropertyStub,
            JSCustomer::JSGetProperty, JSCustomer::JSSetProperty,
            JS_EnumerateStub, JS_ResolveStub, 
            JS_ConvertStub, JSCustomer::JSDestructor
    }
    ;

    用到的回调函数是JSCustomer::JSGetProperty,JSCustomer::JSSetProperty和JSCustomer::JSDestructor。脚本引擎调用JSGetProperty获取属性值,调用JSSetProperty设置属性值,调用JSDestructor析构JavaScript对象。

    JSCLASS_HAS_PRIVATE标志位会让脚本引擎分配一些内存,这样你能够在JavaScript对象中附加一些自己定义数据,比方能够用它来保存类指针。

    回调函数以C++的类静态成员函数方式存在:

    static JSBool JSGetProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp);
    static JSBool JSSetProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp);
    static JSBool JSConstructor(JSContext *cx, JSObject *obj, uintN argc, 
                                jsval 
    *argv, jsval *rval);
    static void JSDestructor(JSContext *cx, JSObject *obj);

    步骤2-初始化你的JavaScript对象

    创建另外一个叫JSInit的静态方法,见以下的样例,该方法将在应用程序创建JavaScript runtime时被调用。

    static JSObject *JSInit(JSContext *cx, JSObject *obj, JSObject *proto);

    JSInit方法的实现大约例如以下:
    JSObject *JSCustomer::JSInit(JSContext *cx, JSObject *obj, JSObject *proto)
    {
        JSObject 
    *newObj = JS_InitClass(cx, obj, proto, &customerClass,
            JSCustomer::JSConstructor, 
    0,
            JSCustomer::customer_properties, JSCustomer::customer_methods,
            NULL, NULL);
        
    return newObj;
    }

    对象在脚本中被具象化(译注:instantiated,简而言之就是对象new出来的时候)的时候,静态方法JSConstructor会被调用。在这种方法中能够用JS_SetPrivate API给该对象附加一些自己定义数据。

    JSBool JSCustomer::JSConstructor(JSContext *cx, JSObject *obj, uintN argc, 
                                     jsval 
    *argv, jsval *rval)
    {
        JSCustomer 
    *= new JSCustomer();

        p
    ->setCustomer(new Customer());
        
    if ( ! JS_SetPrivate(cx, obj, p) )
            
    return JS_FALSE;
        
    *rval = OBJECT_TO_JSVAL(obj);
        
    return JS_TRUE;
    }

    JSConstructor构造方法能够带多个參数,用来初始化类。眼下为止已经在堆上创建了一个指针,还须要一种途径来销毁它,这能够通过JS_Destructor完毕:
    void JSCustomer::JSDestructor(JSContext *cx, JSObject *obj)
    {
        JSCustomer 
    *= JS_GetPrivate(cx, obj);
        delete p;
        p 
    = NULL;
    }

    步骤3-加入属性

    加入一个JSPropertySpec类型的静态成员数组来存放属性信息,同一时候定义属性ID的枚举变量。

    static JSPropertySpec customer_properties[];
    enum
    {
        name_prop,
        age_prop
    }
    ;

    在实现文件里例如以下初始化该数组:

    JSPropertySpec JSCustomer::customer_properties[] = 

        
    "name", name_prop, JSPROP_ENUMERATE },
        
    "age", age_prop, JSPROP_ENUMERATE },
        
    0 }
    }
    ;

    数组的最后一个元素必须为空,当中每一个元素是一个带有3个元素的数组。第一个元素是给JavaScript用的名字。第二个元素是该属性的唯一ID,将传递给回调函数。第三个元素是标志位,JSPROP_ENUMERATE代表脚本在枚举Customer对象的全部属性时能够看到该属性,也能够指定JSPROP_READONLY来表明该属性不同意被脚本程序改变。

    如今能够实现该属性的getting和setting回调函数了:

    JSBool JSCustomer::JSGetProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
    {
        
    if (JSVAL_IS_INT(id)) 
        
    {
            Customer 
    *priv = (Customer *) JS_GetPrivate(cx, obj);
            
    switch(JSVAL_TO_INT(id))
            
    {
            
    case name_prop:

                
    break;
            
    case age_prop:
                
    *vp = INT_TO_JSVAL(priv->getCustomer()->GetAge());
                
    break;
            }

        }

        
    return JS_TRUE;
    }



    JSBool JSCustomer::JSSetProperty(JSContext 
    *cx, JSObject *obj, jsval id, jsval *vp)
    {
        
    if (JSVAL_IS_INT(id)) 
        
    {
            Customer 
    *priv = (Customer *) JS_GetPrivate(cx, obj);
            
    switch(JSVAL_TO_INT(id))
            
    {
            
    case name_prop:
                
    break;
            
    case age_prop:
                priv
    ->getCustomer()->SetAge(JSVAL_TO_INT(*vp));
                
    break;
            }

        }

        
    return JS_TRUE;
    }

    建议在属性的回调函数中返回JS_TRUE。假设返回JS_FALSE,则当该属性在对象中没找到时(脚本引擎)不会进行搜索。

    步骤4-加入方法

    创建一个JSFunctionSpec类型的静态成员数组:

    static JSFunctionSpec customer_methods[];

    在实现文件里例如以下初始化该数组:

    JSFunctionSpec wxJSFrame::wxFrame_methods[] = 
    {
        
    "computeReduction", computeReduction, 100 },
        
    0 }
    }
    ;

    最后一个元素必须为空,当中每一个元素是一个带有5个元素的数组。第一个元素是给脚本程序用的方法名称。第二个是一个全局或者静态成员函数的名称。第三个是该方法的參数个数。最后两个能够忽略。

    在类中创建一个静态方法:

    static JSBool computeReduction(JSContext *cx, JSObject *obj, uintN argc, 
                                   jsval 
    *argv, jsval *rval);

    该函数成功时返回JS_TRUE,否则返回JS_FALSE。注意真正的JavaScript方法的返回值保存在rval參数中。

    该方法的一个实现样例:

    JSBool JSCustomer::computeReduction(JSContext *cx, JSObject *obj, uintN argc, 
                                        jsval 
    *argv, jsval *rval)
    {
        JSCustomer 
    *= JS_GetPrivate(cx, obj);
        
    if ( p->getCustomer()->GetAge() < 25 )
            
    *rval = INT_TO_JSVAL(10);
        
    else
            
    *rval = INT_TO_JSVAL(5);
        
    return JS_TRUE;
    }

    使用样例

    以下的脚本使用了前面创建的对象:

    var c = new Customer();
    c.name 
    = "Franky";
    c.age 
    = 32;
    var reduction = c.computeReduction();

    别忘了在创建context时初始化JavaScript对象:

    JSObject *obj = JSCustomer::JSInit(cx, global);

    类常量

    JavaScript类型

    这一章解释在JavaScript中会用到的几种类型:Integer,String,Boolean,Double,Object和Function。

    构建中。。。。。。

    垃圾回收

    构建中。。。。。。

    下载

    main.cpp演示怎样运行一个javascript程序。JSCustomer.h演示Customer的JavaScript类的定义。JSCustomer.cpp演示JSCustomer的实现。Customer.h是Customer C++类的定义。example.js示例脚本程序。

     
  • 相关阅读:
    一行代码更改博客园皮肤
    fatal: refusing to merge unrelated histories
    使用 netcat 传输大文件
    linux 命令后台运行
    .net core 使用 Nlog 配置文件
    .net core 使用 Nlog 集成 exceptionless 配置文件
    Mysql不同字符串格式的连表查询
    Mongodb between 时间范围
    VS Code 使用 Debugger for Chrome 调试vue
    css权重说明
  • 原文地址:https://www.cnblogs.com/mengfanrong/p/4086452.html
Copyright © 2011-2022 走看看