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

译序

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

 

转载于:https://www.cnblogs.com/mfrbuaa/p/4251455.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/257704.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Android 虚拟机学习总结Dalvik虚拟机介绍

1、Dalvik虚拟机与Java虚拟机的最显著差别是它们分别具有不同的类文件格式以及指令集。Dalvik虚拟机使用的是dex&#xff08;Dalvik Executable&#xff09;格式的类文件&#xff0c;而Java虚拟机使用的是class格式的类文件。一个dex文件能够包括若干个类。而一个class文件仅仅…

des vue 加密解密_vue DES 加密

ECB模式import cryptoJs from crypto-js// DES加密export const encryptDes (message, key) > {var keyHex cryptoJs.enc.Utf8.parse(key)var option { mode: cryptoJs.mode.ECB, padding: cryptoJs.pad.Pkcs7 }var encrypted cryptoJs.DES.encrypt(message, keyHex, op…

使用jQuery清空表单

$(#theform)[0].reset(); reset()这个函数的作用是将表单的值重置&#xff0c;变为默认值&#xff0c; 例&#xff1a; <input type"text" value"姓名"> 这个标签的默认值就是”姓名“&#xff0c;如果使用上面的方法&#xff0c;就会重置为“姓名”…

MobX快速入门教程(重要概念讲解)

转载请注明原文地址&#xff1a;http://www.cnblogs.com/ygj0930/p/7372119.html 一&#xff1a;Mobx工作流程图 二&#xff1a;MobX涉及到的概念 1:状态state 组件中的数据。 2:被观察observable 被observable修饰的state数据将会暴露给整个app&#xff0c;各观察者组件都可以…

CentOS工作内容(七)禁用IPV6

CentOS工作内容&#xff08;七&#xff09;禁用IPV6 用到的快捷键 tab 自动补齐(有不知道的吗) ctrla 移动到当前行的开头(a ahead) ctrle 移动到当前行的开头(e end) ctrlu 删除(剪切)此处至开始所有内容 复制进来&#xff1a;按一下鼠标右键粘贴到SercureCRT 复制出去&#x…

循环结束后变回去 设置一个值_VBA掌握循环结构,包你效率提高500倍

这是系列免费教程《Excel VBA&#xff1a;办公自动化》&#xff0c;还是老规矩&#xff0c;看看我们走到哪里了。1.认识VBA&#xff1a;什么是VBA&#xff1f;2.这些掌握了&#xff0c;你才敢说自己懂VBA3.VBA变量5年踩坑吐血精华总结4.VBA中重要的强制申明&#xff0c;谁看谁明…

连接到kali linux服务器上的MySQL服务器错误

前言&#xff1a;想把数据库什么的都放在虚拟机kali Linux里&#xff0c;但无奈出了好多错误。 首先&#xff1a;可以参照上一篇文章开启kali服务器端的远程连接功能&#xff0c;上一篇文章 然后&#xff1a;使用window端的sqlyog&#xff08;MySQL图形化连接工具&#xff09;连…

dedecms后台怎么添加发布软件?织梦后台软件内容管理

使用织梦cms有很多的功能&#xff0c;其中有一个是在dedecms后台添加发布软件&#xff0c;然后在前台大家可以直接下载软件&#xff0c;在织梦cms后台怎么添加发布软件呢&#xff1f;下面是织梦软件内容管理的主要操作步骤。使用织梦cms有很多的功能&#xff0c;其中有一个是在…

301 302区别_如何正确理解301,302和canonial标签

今天我们来学习一下几个比较容易混淆的页面跳转标签&#xff0c;301&#xff0c;302&#xff0c;relcanonial。在谷歌SEO里面&#xff0c;我们比较容易常见的是第一个301&#xff0c;302和canonial出现的比较少&#xff0c;但是不代表不存在&#xff0c;我会尝试从以下价格方面…

ffmpeg文档08-表达式计算/求值

8 表达式计算/求值 在计算表达式时&#xff0c;ffmpeg通过libavutil/eval.h接口调用内部计算器进行计算。 表达式可以包含一元运算符、运算符、常数和函数 两个表达式expr1和expr2可以组合起来成为"expr1;expr2" &#xff0c;两个表达式都会被计算&#xff0c;但是新…

为什么手机游戏手柄没有流行起来?

问答社区知乎上有人提了一个问题&#xff0c;“为什么手机用游戏手柄没有流行&#xff1f;” Ta找了不少论证&#xff1a;1&#xff09;手机用户数量很大&#xff1b;2&#xff09;大量用户在手机上花费最多时间的是玩游戏&#xff1b;3&#xff09;游戏机平台&#xff08;的游…

c++排序算法ppt_C/C++学习教程:C语言排序算法—插入排序算法

前言&#xff1a;插入排序算法是所有排序方法中最简单的一种算法&#xff0c;其主要的实现思想是将数据按照一定的顺序一个一个的插入到有序的表中&#xff0c;最终得到的序列就是已经排序好的数据。直接插入排序是插入排序算法中的一种&#xff0c;采用的方法是&#xff1a;在…

python函数参数

1.位置参数 2.默认参数 指向参数为不可变对象 3.可变参数 **args 一个列表list或是元组tuple 4.关键字参数 **kw,是一个字典dict 5.命名关键字参数 *, 转载于:https://www.cnblogs.com/aliy-pan/p/5198025.html

Python 常用函数 configparser模块

使用ConfigParser模块读写ini文件 ConfigParserPython的ConfigParser Module中定义了3个类对INI文件进行操作。分别是RawConfigParser、ConfigParser、SafeConfigParser。模块所解析的ini配置文件是由多个section构成&#xff0c;每个section名用中括号‘[]’包含&#xff0c;每…

自制Unity小游戏TankHero-2D(3)开始玩起来

自制Unity小游戏TankHero-2D(3)开始玩起来 我在做这样一个坦克游戏&#xff0c;是仿照&#xff08;http://game.kid.qq.com/a/20140221/028931.htm&#xff09;这个游戏制作的。仅为学习Unity之用。图片大部分是自己画的&#xff0c;少数是从网上搜来的。您可以到我的github页…

mysql按月分列统计_实现mysql按月统计的教程

mysql有个字段是DATETIME类型&#xff0c;要实现可以按月统计&#xff0c;该怎么写sql语句&#xff1f;select month(f1) from tt group by month(f1)or select DATE_FORMAT(f1,%m) from tt group by DATE_FORMAT(f1,%m)比如数据库的为2008-01-15 12&#xff1a;10&#xff1a;…

Log4j的扩展-支持设置最大日志数量的DailyRollingFileAppender

Log4j现在已经被大家熟知了&#xff0c;所有细节都可以在网上查到&#xff0c;Log4j支持Appender&#xff0c;其中DailyRollingFileAppender是被经常用到的Appender之一。在讨论今天的主题之前&#xff0c;我们先看下另外一个Appender。 最常用的Appender——RollingFileAppend…

VirtualBox虚拟机安装CentOS 7

新建虚拟机 因为比较简单&#xff0c;所以对于VirtualBox就不做过多介绍了&#xff0c;直接下载安装即可&#xff0c;安装好之后打开Oracle VM VirtualBox管理器&#xff0c;点击新建&#xff0c;选择Red Hat&#xff08;根据windows主机选择 32/64 bit&#xff0c;通常会自动识…

mysql 指定账户已存在_安装mysql时告诉我指定的账户已存在?

{"moduleinfo":{"card_count":[{"count_phone":1,"count":1}],"search_count":[{"count_phone":4,"count":4}]},"card":[{"des":"阿里云数据库专家保驾护航&#xff0c;为用户…

C语言:用字符读取流和输出流来读写入数据。(文本文件)

/* 文件的几种操作模式: r:只读 w:只写 rw:可读可写 文件的分类&#xff1a; t:文本文件(字符文件) b:二进制文件(字节文件)注意&#xff1a; 采用只读方式打开文件时,如果源文件不存在,打开文件会失败&#xff01; 采用只写方式打开文件时,不管源文件存不存在,都不会失败…