首页 > 编程语言 >SpiderMonkey-让你的C++程序支持JavaScript脚本

SpiderMonkey-让你的C++程序支持JavaScript脚本

时间:2022-12-27 15:35:00浏览次数:67  
标签:JavaScript obj JSCustomer C++ JS SpiderMonkey cx JSObject


译序

有些网友对为什么D2JSP能运行JavaScript脚本程序感到奇怪,因此我翻译了这篇文章,原文在​​这里​​​。这篇教程手把手教你如何利用SpiderMonkey创建一个能执行JavaScript脚本的C++程序,并让JavaScript脚本操纵你的C++程序的内部数据、操作。从这篇教程可以看到在SpiderMonkey引擎的帮助下,让C++程序支持JavaScript脚本是一件很容易的事,更棒的是SpiderMonkey也可以在Macintosh和Unix平台使用。
SpiderMonkey是​​​Gecko​​​(​​Firefox​​​浏览器的内核)的JavaScript脚本引擎,详细文档请看​​这里​​。

以下为翻译内容。


本教程的目的是教你如何用JavaScript做为脚本语言使你的C++程序自动化。

SpiderMonkey

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

SpiderMonkey的最新版本可在​​这里​​下载。它是以源代码形式发布的,因此你必须自己编译它(译注:其实网上有很多编译好的二进制版本,google一下js32.dll就可找到)。Visual C++用户可以在src目录下找到Workspace项目工程文件来编译,编译结果会产生一个叫'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,  0 ,  0 ); 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 *p = 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 *p = 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, 1, 0, 0 },
    { 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 *p = 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​​演示例子脚本程序。


标签:JavaScript,obj,JSCustomer,C++,JS,SpiderMonkey,cx,JSObject
From: https://blog.51cto.com/u_15923385/5972857

相关文章

  • JavaScript-C/C++ (SpiderMonkey) 引擎嵌入开发指南(中文向导)
    JavaScript-C/C++引擎概览本文档提供了一个JavaScript(JS)引擎的C语言实现的概述,他介绍了你如何在你的应用程序中嵌入脚本引擎来让它们可以使用JS。有两大理由让你在应用程......
  • JavaScript 深拷贝的循环引用问题
    如果说道实现深拷贝最简单的方法,我们第一个想到的就是JSON.stringify()方法,因为JSON.stringify()后返回的是字符串,所以我们会再使用JSON.parse()转换为对象,如下代码:let......
  • [C++] C++中关于内存的初解
    内存四区1.代码区存放函数体的二进制代码,由操作系统进行管理。代码区是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码就可以。代码是只读的,只读的......
  • SpiderMonkey 脚本化您的应用
    和其他的JavaScript引擎一样,SpiderMonkey不直接提供像DOM这样的对象,而是提供解析,执行JavaSccript代码,垃圾回收等机制。SpidlerMonkey是一个在Mozilla之下的开源......
  • go-zero一键生成 Go, iOS, Android, Kotlin, Dart, TypeScript, JavaScript 代码,并可
    go-zero是一个集成了各种工程实践的web和rpc框架。通过弹性设计保障了大并发服务端的稳定性,并经受了充分的实战检验(好未来-晓黑板)。go-zero包含极简的API定义......
  • 如何在iOS和Android上选择一个JavaScript 引擎进行应用开发
    在我开始使用​​OpenAphid-Engine​​​的时候,已经有几种类似的iOS/Android项目.这些商业项目或者开源项目使用JavaScript实现代码特性。比如,​​Titanium​​​ 和​​P......
  • 【cocos2d-x从c++到js】注册函数
    前面的文章中讲过,在游戏启动时,会调用大量的addRegisterCallback函数,向SpiderMonkey注册Cocos2d-x引擎的函数。​​ScriptingCore*sc=ScriptingCore::getInsta......
  • C++11 新特性之Range-based for loops
    声明:本文少量代码转载自AlexAllain的文章 ​​http://www.cprogramming.com/c++11/c++11-ranged-for-loop.html​​很多语言都有Range-basedforloops这个功能,现在C++......
  • C++11:列表初始化
    在C++98/03中,对象初始化方法有很多种,如下代码所示://初始化列表inti_arr[3]={1,2,3};//普通数组structA{intx;structB{inti;......
  • C++和Objective-C混编(官方文档翻译)
    UsingC++WithObjective-C  苹果的Objective-C编译器允许用户在同一个源文件里自由地混合使用C++和Objective-C,混编后的语言叫Objective-C++......