JavaScript-C/C++引擎概览
本文档提供了一个JavaScript(JS)引擎的C语言实现的概述,他介绍了你如何在你的应用程序中嵌入脚本引擎来让它们可以使用JS。有两大理由让你在应用程序中嵌入JS引擎:使用脚本来自动操作你的应用程序;同时使用JS引擎和脚本无论何时都可以提供跨平台的功能并消除了应用程序解决方案对平台的依赖性。
受支持的JavaScript版本
本JS引擎支持从JS 1.0版到JS1.4。JS 1.3和更高版本符合ECMAScript-262规范。JS引擎解析、编译和执行包含JS语句和函数的脚本。这个引擎可以处理要用来执行脚本的JS数据类型和对象内存分配,同时它可以清除——垃圾回收——内存中已经不需要的数据类型和对象。
你如何使用这个引擎?
通常,你将JS引擎作为一个共享的资源进行构建。例如,在Windows和Windows NT上,这个引擎是一个DLL文件,在Unix上是一个共享库。然后你把你的应用程序和他连接,同时嵌入式JS引擎应用程序编程接口(API)就可以在你的应用程序中调用了。JS引擎的API提供了以下几种分类的函数:
- 数据类型操作
- 运行时控制
- 类和对象创生的维护
- 函数和脚本执行
- 字符串处理
- 错误处理
- 安全性控制
- 调试支持
JS_NewRuntime
函数来新建和初始化JS引擎。其他功能分类,像安全控制,提供一些可选的特性,你可以根据需要在你的应用程序中使用它们。
这个引擎和应用程序有什么关系?
从概念上来讲,JS引擎在你的系统上是一个共享资源。通过在你的应用程序中嵌入引擎API命令你可以向JS引擎传递处理的请求。这个引擎,反过来,处理你的请求,并把返回值或者状态信息返回给你的应用程序。图1.1描述了它们一般的关系:
图 1.1
例如,假设你正在使用JS引擎来使你的应用程序能通过JS脚本自动运行,同时假设你的应用程序运行一个脚本来对一个用户进行身份验证并且设置一个用户对这个应用程序的访问权限。首先,你的应用程序可能新建一个代表用户的自定义JS对象,包括了用户的名字、ID、访问权限和一个可能的用户拥有权限在应用程序中使用的函数的列表。
JS_NewObject
的调用来新建一个自定义对象。当JS引擎新建了这个对象,他返回一个指针给你的应用程序。你的应用程序可以再次调用JS引擎来执行使用这个对象的脚本。例如,在建立了用户对象之后,你的应用程序会立刻给JS_EvaluateScript
传递一个脚本来立刻编译执行。那个脚本可以获得并验证用户信息,然后建立用户对其他应用程序特性的访问权限。jsapi.h
还假设应用程序对引擎进行的第一个调用已经初始化了JS运行时。
当JS引擎接受到了一个初始化的请求时,他会为JS运行时分配内存。图1.2描述了这个过程:
图 1.2
这个运行时是一个内存空间,在其中可以维护你的应用程序所使用的变量、对象和上下文。一个上下文是指,针对JS引擎所使用的线程的脚本执行状态。每个同时存在的脚本或者线程都必须有它自己的上下文。一个单独的JS运行时可以包含很多上下文、对象和变量。
JS_NewContext
来至少创建一个上下文。实际你需要的上下文数量由你的应用程序中所期望同时运行的脚本的数量决定。从另一方面说,如果同一时间只有一个脚本被编译执行,那么你就知需要建立单独的一个上下文,你可以对每个脚本重复使用它。JS_InitStandardClasses
实现。内置的对象有Array
,Boolean
,Date
,Math
,Number
,和String
字符串对象,大多数脚本都会用到。JS_InitClass
来在运行时设立这个类,然后调用JS_NewObject
来在引擎中新建你这个自定义对象的实例。最后,如果你的对象有一些属性,你也许要通过调用JS_SetProperty
来设置他们的默认值。
即使你在创建一个对象的时候给JS引擎传递了一个特定的上下文,最后这个对象还是独立于这个上下文存在的。任何脚本都可以和任意上下文相关联来访问任何对象。图1.3描述了脚本和运行时、上下文以及对象之间的关系。
图 1.3
如图1.3所示,脚本和上下文完全是互相独立存在的及时他们可以访问相同的对象。在给定的运行时中,一个应用程序可以任意未分配的上下文来访问任何对象。也可能有时你想确保能为独占的使用而保留某些上下文和对象。在这些情况下,给你的应用程序新建单独的运行时:一个针对共享上下文和对象,另一个(或者更多的,取决于你的应用程序的需求)针对私有的运行时和对象。
注意:同一时间只能有一个线程被授权访问特定上下文。
构建引擎
在你可以在你的应用程序中使用JS之前,你必须将JS引擎构建成一个可共享的库。在大多数情况下,引擎代码已经包括了Make文件来自动构建。
js
源代码目录包括了一个基本的Gnu Make文件——Makefile.ref
和一个config
目录。config
目录包括了平台特定的.mk
文件来配合Makefile.ref
对你的环境进行构建。在Windows NT下,NMake文件是js.mak
。readme
文件,也许其中包括了和更新的编译指导或者其他信息。
嵌入引擎有什么必要条件?
如果要让你的应用程序可以执行JS,就要在你的应用程序代码中嵌入合适的引擎。嵌入一般有五步:
#include jsapi.h
- 来确保编译器知道有哪些引擎的API可以调用。极少部分特殊的JS引擎工作时会要求你包含额外的头文件。例如,要在你的应用程序中调用JS调试器,你要在合适的模块里面包含
jsdbgapi.h
- 。
不应该被引用。这样做可能会使你的程序依赖于引擎内部的接口,而这些接口可能随着版本发布而更改。
jsapi.h
- 中定义的JS数据类型来声明结构和变量。
- 使用JavaScript编写特定应用的对象。这些对象常常会与操作在你C程序中的结构的结构和方法进行通讯,特别是如果你在使用JS引擎来自动操作你的应用程序。
- 在程序代码中嵌入合适的JS引擎API调用和变量引用,包括初始化内置JS对象,和创建组成任何应用程序要用的自定义对象。
- 大多数JS引擎调用都会返回一个值。如果这个值是零或者空,它通常表示一个错误的情况发生了。如果值非零,它一般表示成功;在这些情况下,返回的值常常会是你的程序需要使用的指针,或者存起来留以后引用。很重要的是,你的程序至少应该每次检查JS调用返回的值。
客户端JavaScript指导,如果要得到关于编写服务器端对象,见服务器端JavaScript指导。
.
.
.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* 包含JS引擎API头文件 */
#include "jsapi.h"
.
.
.
/* 主程序建立全局JS变量,包括运行时,
* 一个上下文,和一个全局对象,然后初始化JS运行时,
* 并创建一个上下文. */
int main(int argc, char **argv)
{
int c, i;
/*建立全局的JS变量,包括全局和自定义对象 */
JSVersion version;
JSRuntime *rt;
JSContext *cx;
JSObject *glob, *it;
JSBool builtins;
/* 初始化JS运行时,并返回结果给rt */
rt = JS_NewRuntime(8L * 1024L * 1024L);
/* 如果rt没有值,结束程序 */
if (!rt)
return 1;
/* 新建一个上下文并且把它和JS运行时相关联 */
cx = JS_NewContext(rt, 8192);
/* 如果cx没有值,在此结束程序 */
if (cx == NULL)
return 1;
/* 新建全局对象 */
glob = JS_NewObject(cx, clasp, NULL, NULL);
/* 初始化内置JS对象和全局对象 */
builtins = JS_InitStandardClasses(cx, glob);
.
.
.
return 0;
}
js.c
,这个范例应用的源代码是包含在JS引擎的源代码中的。
理解关键嵌入开发概念
大多数你要创建的JavaScript应用,你都会想要遵循一些权威的的JS API嵌入实践。以下部分讲述了任何程序中都要使用到的API调用。
main函数像一种三明治,在任何你提供的功能前后先初始化最后释放JS运行时:
int main(int argc, char **argv)
{
int c, i;
/*建立全局JS变量,包括全局对象global和自定义对象 */
JSVersion version;
JSRuntime *rt;
JSContext *cx;
JSObject *glob, *it; .
.
. /* 初始化JS运行时,并把返回的结果放在rt中 */
rt = JS_NewRuntime(8L * 1024L * 1024L); /* 如果rt没有值,程序结束。 */
if (!rt)
return 1; .
.
. /* 建立一个上下文 */
cx = JS_NewContext(rt, 8192); /* 如果cx值为空,则结束程序 */
if (cx == NULL)
return 1; /* 初始化内置JS对象和全局对象 */
builtins = JS_InitStandardClasses(cx, glob); .
.
. /* 把你的代码扔这里,包括你要用来创建自定义JS对象的JS API函数调用。
* JS对象模型在这里开始。 */ .
.
./* 在退出应用程序之前,释放JS运行时 */
JS_DestroyRuntime(rt);
正如这个例子所描述的,嵌入了JS引擎的函数的应用程序要负责建立JS运行时,这是他最先要完成的动作之一,同时它还要负责在退出之前释放运行时。一般来说,确保运行时被初始化和释放的最佳位置是在中央JS调度程序的模块中嵌入必要的调用,无论你将使用哪一个模块作为在应用程序的中央调度模块。
在你初始化了运行时之后,你可以建立应用程序的JS对象模型。这个对象模型决定了你的JS对象互相之间的关系。JS对象本质上是分层次的。所有的JS对象都是默认与全局(global)对象相关的,他们都是全局对象的子孙。当你初始化标准JS类的时候,你会自动获得一个全局对象:
builtins = JS_InitStandardClasses(cx, glob);
全局对象会建立一些基本属性和方法,其他对象都会继承这些属性和方法。当你创建你自己的对象时,他们可以自动使用这些已经定义在全局对象上的属性和方法。你可以通过在自定义对象上重新对他们进行定义来覆盖这些默认属性和方法,否则可以直接接受默认的赋值。
你也可以基于其他的内置JS对象新建自定义对象,或者基于其他自定义对象来新建对象。无论哪种情况,你新建的对象在继承链中将继承他祖先的所有属性和方法,一直追溯到全局对象。如果要了解更多关于全局和自定义对象地内容,请参见“初始化内置和全局JS对象”以及“创建和初始化自定义对象”。
管理一个运行时
JS_NewRuntime
。JS_NewRuntime
有一个参数,是一个无符号整数,它指明了在垃圾收集发生之前,分配给运行时的内存最大数值,单位是字节。例如:rt = JS_NewRuntime(8L * 1024L * 1024L);
JS_NewRuntime
也会返回一个值,这个值是一个指向新建的运行时的指针。一个非空的返回值表示运行时被成功创建了。JS_NewRuntime
并把返回值存在不同的指针中。JS_DestroyRuntime
来释放运行时,当运行时不再需要的时候:JS_DestroyRuntime(rt);
如果你使用了多个运行时,要确保在结束应用程序前,每一个都被正确释放了。
管理上下文
几乎所有的JS API调用都要求你传送一个上下文作为参数。在JavaScript引擎中,一个上下文唯一对应一个脚本。引擎把上下文信息传送给运行脚本的那个线程。每个同步执行的脚本必须被分配一个唯一的上下文。当一个脚本执行完之后,他的上下文就不再被使用了,这时候这个上下文就可以再次被分配给一个新的脚本,或者可以释放他。
JS_NewContext
函数。该函数有两个参数:一个指针指向上下文所需结合的运行时,和为上下文分配的栈空间的大小,以字节为单位。如果成功,函数返回新建的上下文的指针。例如:JSContext *cx;
.
.
.
cx = JS_NewContext(rt, 8192);
运行时必须已经存在。你为上下文指定的栈的大小应该足够容纳使用这个上下文的脚本所创建的任何变量和对象。注意和分配和维护上下文相关有一个特定的数量,因为你可能要:
- 只创建同一时刻在你的应用程序中所需要的数量一样多的上下文。
- 只要上下文有可能被应用程序用到,就保留他们,而不是每当需要的时候再重新新建不需要了就立刻销毁。
JS_DestroyContext
来释放上下文。这个函数带一个参数,也就是指向要销毁的上下文的指针:JS_DestroyContext(cx);
JS_GetRuntime
,并且把上下文作为参数传递给他。JS_GetRuntime
会返回一个指向某个合适的运行时的指针,如果存在的话:rt = JS_GetRuntime(cx);
JS_SetContextPrivate
函数来建立一个指向上下文使用的私有数据的指针,并调用JS_GetContextPrivate
函数来获取这个指针,这样就可以访问这些数据了。你的应用程序要负责创建和管理这个可选的私有数据。
若要创建私有数据并把它和一个上下文关联:
- 根据需要建立私有数据,可以使用一个普通的 C void 指针变量。
JS_SetContextPrivate
- ,并指明通过哪个上下文来建立私有数据,并给出指向数据的指针。
例如:
JS_SetContextPrivate(cx, pdata);
JS_GetContextPrivate
函数,并把上下文作为参数传递给他。该函数会返回指向私有数据的指针:
pdata = JS_GetContextPrivate(cx);
初始化内置的和全局的JS对象
Array
对象让你更方便地在JS引擎中创建和处理数组结构。类似,Date
对象提供了一个统一的处理日期数据的机制。要查阅引擎支持的内置对象的完整列表,请参看JS_InitStandardClasses。
JS引擎始终使用函数和全局对象。一般来说,全局对象存在于JS脚本的场景背后,为所有其它JS对象提供了一个默认的空间范围和存储了你在程序中创建和使用的全局变量。在你创建你自己的对象之前,你需要初始化全局对象。函数对象将启用对象支持和构造器调用。
JS_InitStandardClasses
, 这个API调用将初始化全局和函数对象还有引擎内置的对象,这样你的应用程序就可以使用他们了:
JSBool builtins; . . . builtins = JS_InitStandardClasses(cx, glob);
JS_InitStandardClasses
会返回一个JS boolean值来表示初始化是否成功。window
。若要为你的应用程序更改全局对象,可以调用JS_SetGlobalObject
函数。详细信息请查阅JS_SetGlobalObject的参考条目。
创建和初始化自定义对象
除了可以使用引擎内置对象之外,你还可以新建、初始化和使用你自己的JS对象。如果你使用JS引擎处理脚本对你的应用进行自动化操作,这点尤其重要。自定义JS对象可以提供最直接的程序服务,另外他们也可以作为你的程序服务的一个接口。例如,一个自定义JS对象提供了某种直接的服务,像处理应用程序所有的网络访问、作为数据服务的中间层。也可以是使用一个JS对象映射到应用程序中以后的数据和函数中,这样能为C代码提供一个面向对象的接口。这样一个自定义对象对应用程序自身来说扮演了一个接口的角色——从应用程序中把值传递给用户,并且接受和处理用户的输入然后再返回给应用程序。这种对象也可以用来对应用程序内部的函数进行访问控制。
有两种方法可以创建自定义对象:
- 写一个JS脚本,用来创建对象,以及他的属性、方法和构造器,然后把这个脚本在运行时传递给JS引擎。
- 在你的程序中嵌入定义对象的属性和方法的代码,调用引擎来初始化新对象,然后通过其它的引擎调用来设置对象的属性。这个方法的一个好处是你的程序可以包含直接处理所嵌对象的本地方法。
JS_AddRoot
或
JS_AddNamedRoot
调用来确定这个对象的“根”。使用这两个函数会确保JS引擎去跟踪这些对象并在适当的时候通过垃圾收集过程中清理掉他们。
从脚本中建立一个对象
要从脚本中创建自定义JS对象的一个原因是,只需要一个在脚本运行期间存在对象。要创建这种持续在脚本调用期间的对象的话,你也可以直接在你的应用程序中嵌入对象的代码,而不用使用一个脚本。
注意:你同样可以使用脚本创建持久对象。
要使用脚本创建一个自定义对象:
- 定义和说明对象。他的目的是什么?他的数据成员(属性)有哪些?他有哪些方法(函数)?他是否需要一个运行时构造函数?
- 编写出定义和创建对象的JS脚本。例如:
function myfun(){
var x = newObject();
.
.
.
}
注意:使用JavaScript编写的对象并不在应用程序嵌入JS引擎的代码中。关于对象编写的更多内容,请参阅《客户端JavaScript指导》和《服务器端JavaScript指导》。
- ,
- 或者2.) 使用
- 或者
- ,来一次性编译脚本,然后可以用一个独立的函数调用
- . 来重复执行已经编译的代码。这些调用的“UC”版可以提供对统一码脚本的支持。
你使用脚本创建的一个对象只可以在脚本的生命周期内启用,或者也可以在脚本运行结束之后持久化。一般来说,一旦脚本运行结束,他的所有对象都会被销毁。在大部分情况下,这种行为正是你的应用程序需要的。然而,在其他的情况下,你可能希望某对象持续在脚本之间,或者你的应用程序的整个生命周期。这样的话你需要直接在你的应用程序中嵌入对象创建代码,或者你必须把对象直接连接到全局对象这样他会一直持续只要全局对象本身存在。
在应用程序中嵌入一个自定义对象
当必须进行对象持久化时,或者你认为需要对几个脚本都可用的对象时,嵌入一个自定义JS对象在应用程序中是很有用的。例如,一个代表了用户的ID和访问权限的自定义对象可能会在应用程序的整个生命期中都会用到。他事先一次性创建和组装了对象,节省了很多时间,而不用每次要检验用户ID或者权限时一遍又一遍用脚本创建对象。
一种在应用程序中嵌入自定义对象的方法是:
JSPropertySpec
数据类型,并把它和属性的信息组装成对象的属性,包括参数的获取(get)和设置(set)方法的名称。
JSFunctionSpec
数据类型,并把它和方法的信息组装成对象使用的方法。
- 创建一个实际的C函数用来处理对象的方法调用。
JS_NewObject
或者
JS_ConstructObject
来实例化这个对象。
JS_DefineFunctions
来创建这个对象的方法。
JS_DefineProperties
来创建这个对象的属性。
描述持久的自定义JS对象的代码必须放在应用程序执行的开始部分附近,在任何依赖于该对象的代码之前。嵌入的实例化和组装自定义对象的引擎调用也应该出现在任何依赖这个对象的代码之前。
注意:在大多数情况下还有一个更方便的在程序代码中创建自定义对象的方法是调用JS_DefineObject来创建对象,然后反复调用JS_SetProperty来设置对象的属性。关于定义一个对象的更多的信息,参见JS_DefineObject。关于设置对象属性的更多信息,参见JS_SetProperty。
为对象提供私有数据
JS_SetPrivate
来建立一个指向私有数据的指针,并且调用JS_GetPrivate
来获得这个指针这样就可以访问数据了。你的应用程序要对这些可选的私有数据的创建和管理负责。
要创建私有数据并把它和一个对象相关联的话:
- 根据需要建立私有数据,可以使用一个普通的 C void 指针变量。
JS_SetPrivate
- , 制定要为那个对象建立私有数据,并给出指向数据的指针。
例如:
JS_SetPrivate(cx, obj, pdata);
JS_GetPrivate
并且把对象作为一个参数传递。这个函数将返回一个指向对象私有数据的指针:
JS_GetPrivate
处理统一码(Unicode)
JS引擎现在提供了很多API函数的支持统一码的版本。这些函数允许你直接给引擎传递使用统一码编码的脚本进行编译和运行。下面的表格列出了标准引擎函数和他们对应的统一码版本:
标准函数 | 统一码支持函数 |
JS_DefineProperty | JS_DefineUCProperty |
JS_DefinePropertyWithTinyId | JS_DefineUCPropertyWithTinyId |
JS_LookupProperty | JS_LookupUCProperty |
JS_GetProperty | JS_GetUCProperty |
JS_SetProperty | JS_SetUCProperty |
JS_DeleteProperty2 | JS_DeleteUCProperty2 |
JS_CompileScript | JS_CompileUCScript |
JS_CompileScriptForPrincipals | JS_CompileUCScriptForPrincipals |
JS_CompileFunction | JS_CompileUCFunction |
JS_CompileFunctionForPrincipals | JS_CompileUCFunctionForPrincipals |
JS_EvaluateScript | JS_EvaluateUCScript |
JS_EvaluateScriptForPrincipals | JS_EvaluateUCScriptForPrincipals |
JS_NewString | JS_NewUCString |
JS_NewStringCopyN | JS_NewUCStringCopyN |
JS_NewStringCopyZ | JS_NewUCStringCopyZ |
JS_InternString | JS_InternUCString |
– | JS_InternUCStringN |
char *
,而统一码版本的函数参数为jschar *
。
操作JS数据类型
JSObject
,jsdouble
, 和
JSString
,对 JavaScript有特殊意义。JSObject
,jsdouble
, 和JSString
有不同的跟踪。引擎周期性地检查这些变量,察看他们是否还在使用中。如果不再使用了,就收集他们,释放存储空间来重新使用。
垃圾收集可以有效重复利用堆的资源,但是过分频繁的垃圾收集也会对性能造成影响。你可以根据JS运行时控制垃圾收集的频率,根据你给程序分配的JS运行时的大小和你应用程序使用的JS变量和对象的数量之间的关系。如果你的程序要创建和使用很多JS对象和变量,你可能就要分配足够大的运行时来减少垃圾收集的可能频率。
注意你的应用程序要在任何时候调用同样能JS_GC或者JS_MaybeGC来强制进行垃圾收集。JS_GC将强制进行垃圾收集。JS_MaybeGC则会根据条件进行垃圾收集,如果你调用这个函数时,初始化时分配的空间的特定比例已经被使用的话,就进行垃圾收集。
操作JS值
jsval
。一个jsval
本质上是一个指向任意JS数据类型(除了整型)的一个指针。对于整型,jsval
直接包含了他自身的整数值。在其他的情况下,指针还会被编码,添加关于它所指的数据的类型的额外信息。使用可以提高引擎的效率,同时也可以让很多API函数来处理不同类型的数据。
引擎API包含了一组用来测试JS值中的JS数据类型的宏。有:
JSVAL_IS_OBJECT
JSVAL_IS_NUMBER
JSVAL_IS_INT
JSVAL_IS_DOUBLE
JSVAL_IS_STRING
JSVAL_IS_BOOLEAN
jsval
,你也可以检测他是否属于一个基本JS数据类型 (JSVAL_IS_PRIMITIVE
)。基本类型包括未定义(undefined)、空(null)、 布尔(boolean)、数值(numeric)和字符串(string)类型。jsval
所指的值是否为NULL
(JSVAL_IS_NULL
) 或者void
(JSVAL_IS_VOID
)。jsval
指向了一个JS数据类型是JSObject
,jsdouble
, 或者jsstr
,你可以将jsval转换成他的内在的类型,只要相应使用JSVAL_TO_OBJECT
,JSVAL_TO_DOUBLE
和JSVAL_TO_STRING
。在某些情况下,你的应用程序或者JS引擎调用要求使用一个特定的数据类型的变量或者参数而非一个jsval
时,就很有用了。类似地,你可以使用OBJECT_TO_JSVAL
,DOUBLE_TO_JSVAL
, 和STRING_TO_JSVAL
, 把JSObject
,jsdouble
, 和jsstr
相应地转换成jsval
。
操作JS字符串
JSString
,一个指向JS字符—jschar
—数组的指针,用来处理支持统一码的字符串。引擎也实现了一系列丰富的通用和统一码字符串管理程序。最后,JS引擎提供了对限定字符串的支持,这可以将两个或多个相同的字符串创建时在内存中共享一个单独的实例。对于JSString
类型的字符串,引擎会跟踪和管理字符串资源。
通常情况下,当你在处理JS引擎使用的字符串时,你应该使用JS API中的字符串处理函数来创建和复制字符串。还有创建以空字符结尾的和特定长度的字符串的例程,以及获取字符串长度和比较字符串。
统一码字符串支持
JS_NewStringCopyN
,相应的统一码版本就是JS_NewUCStringCopyN
。同样有针对限定字符串的支持统一码的API字符串函数。
限定字符串支持
为了节省存储空间,JS引擎提供了对共享一个单独的字符串实例支持,这些字符串属于一些独立的不可变化的文字。这种被共享的字符串被称为“限定字符串”(interned string)。当你觉得某个特定的文本会被创建并且反复在程序中使用多次的话,那可以使用限定字符串。
引擎的API提供了几种工作于限定字符串的函数调用:
- , 用来创建或者复用一个
JSString
- .
- , 用来创建或者复用一个统一码的
JSString
- .
- , 用以创建或者复用一个固定长度的统一码
JSString
- 。
管理安全性
现在使用JavaScript 1.3,JS引擎加入了安全性增强API函数来编译和执行传送给引擎的脚本和函数。JS安全模型是基于Java的基本安全模型的。该模型提供了一个公共安全接口,但是实际的安全控制由你去实现。
在使用JavaScript的应用中使用安全管理的一个常用的方法是比较脚本的来源和限制脚本的交互。例如,你可能会比较两个或多个脚本的代码源并且只允许来自相同的代码源的脚本修改共享代码源的脚本的属性。
如要实现安全JS,请按照以下几步:
JSPrincipals
- 类型的结构体(struct)。
- 把实现了安全信息的函数列表添加到数组中。这些包括了为程序提供原则数组的函数,和使用给定原则的JS对象的引用计数增减机制。
JSPrincipals
- 结构和你的安全信息组装起来。这个信息可以包括一般代码源信息。
JSPrincipals
- 结构。下面的表格列出了这些API函数和他们的作用:
函数 | 目的 |
编译(但是不执行)一个启用安全控制的脚本。 | |
编译(但不执行)一个启用安全控制、统一码编码的脚本。 | |
从一个文本串创建一个启用安全控制的JS函数。 | |
从一个统一码编码的字符串中创建一个带安全信息的JS函数。 | |
编译和执行一个启用安全控制的脚本。 | |
编译并执行一个启用安全控制且用统一码编码的脚本。 |
文章来源:url https://developer.mozilla.org/en-US/docs/SpiderMonkey/JSAPI_User_Guide
实现教程:
SpiderMonkey
SpiderMonkey是Mozilla项目的一部分,是一个用C语言实现的JavaScript脚本引擎,另外还有一个叫做Rhino的Java版本。
首先你要下载SpiderMonkey的最新版本。下载包是以源代码形式分发的。这就需要你自己编译脚本引擎。对于Visual C++用户来说可以在src-目录找到工程文件。编译结果是一个叫作"js32.dll"的动态库。SpiderMonkey还能应用于 Macintosh及Unix平台之上。想了解如何在其他平台编译SpiderMonkey请参阅ReadMe.html。
SpiderMonkey的世界
为了在SpiderMonkey中运行JavaScript代码,应用程序必须有三个要素:JSRuntime,JSContext和全局对象。
运行时环境
JSRuntime,为其中的JavaScript变量、对象、脚本和应用程序中使用的上下文分配空间。每个JSContext和脚本中的每个对象都生活在一个 JSRuntime中。他们不能转移到其他运行时上或在与其它运行时共享。一般来说大多数应用程序只需要一个运行时环境。
上下文
JSContext,就像是一台小机器,它涉及JavaScript代码和对象的很多东西。它可以编译和执行脚本、获取和设置对象属性、调用JavaScript函数、一种类型转换为另一种JavaScript数据、创建对象,等等。几乎所有JSAPI函数都要一个JSContext*作为其第一个参数,就像<stdio.h>中的大多数函数都需要FILE*一样.
全局对象
全局对象包含所有可以在JavaScript代码中使用的类、函数和变量。
"http://www.mozilla.org/"),实际上它是在访问一个全局属性(全局对象的属性),在这里是window。
脚本能看到的全局属性完全由应用程序控制。应用程序首先创建一个对象并加入JavaScript标准类,如Array和Object。然后加入任何程序想加入的自定义的类、函数和变量(象这里的window)。应用程序每次运行js脚本(例如使用JS_EvaluateScript)时提供了该脚本使用的全局对象。至于脚本,它也可以创建自己全局函数和变量。所有的这些函数、类和变量都作为属性存储在全局对象中。
在C++中执行JavaScript脚本
第1步 - 创建JavaScript运行时环境(runtime)
JS_NewRuntime来初始化。它负责分配运行时环境所需的内存资源。你要指定所分配的字节数,超过这个大小时碎片收集器将会执行。
JSRuntime *rt = JS_NewRuntime(1000000L);
if ( rt == NULL )
{
// Do some error reporting
}
第2步 - 创建一个上下文(context)
上下文指定脚本中栈的大小,并分配指定大小的内存作为脚本的执行栈。每个脚本关联于自己所拥有的上下文。当上下文被一个脚本或线程使用时,其他脚本或线程不能使用。当脚本或线程结束,这个上下文就可以被重用于下一个脚本或线程。
JS_NewContext方法创建新的上下文。一个上下文和一个运行时环境关联,你必须指定栈的大小。
JSContext *cx = JS_NewContext(m_rt, 8192);
if ( cx == NULL )
{
// Do some error reporting
}
第3步 - 初始化全局对象(global object)
在脚本执行前,你必须初始化JavaScript普通函数并创建脚本中的大量对象。
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_EvaluateScript的返回值,运行成功是 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对象
创建一个允许被JavaScript脚本使用的C++类。
JSClass结构来定义JavaScript对象。我们要把它作为公共静态(public static)成员,因为这个结构要被其他类使用。另外它还可以被用于其他类来确定对象类型。(参考 JS_InstanceOf
// 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 对象. 你可以用this存储一个指向你的对象的指针.
回调是静态成员函数:
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 运行时环境.
static JSObject *JSInit(JSContext *cx, JSObject *obj, JSObject *proto);
实现代码如下
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;
}
JS_SetPrivate
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;
}
这个构造器方法可以有多个参数, 能用于初始化你的类. 现在你已经在堆上创建了一个指针, 你也需要一种途径销毁这个指针. 这可以通过静态方法 JS_Destructor 来完成.
void JSCustomer::JSDestructor(JSContext *cx, JSObject *obj)
{
JSCustomer *p = JS_GetPrivate(cx, obj);
delete p;
p = NULL;
}
第3步 - 增加属性
JSPropertySpec的静态数组成员。这个数组将包含属性信息。再创建一个属性ID号的枚举(enum).
static JSPropertySpec customer_properties[];
enum{name_prop,age_prop};
在实现文件中初始化这个数组,代码如下
JSPropertySpec JSCustomer::customer_properties[] =
{
{ "name", name_prop, JSPROP_ENUMERATE },
{ "age", age_prop, JSPROP_ENUMERATE },
{ 0 }
};
数组的最后一个元素必须是空(NULL). 每个元素包含另一个有 3 个元素的数组. 第一个元素是名字,将在 JavaScript 脚本中使用。第二个元素是属性的唯一ID号, 将被传递到回调函数中。第三个元素是一个标志,JSPROP_ENUMERATE 表示脚本中当枚举Customer对象的这个属性时是可见的,就是可以用在脚本中的for 或 in 语句中. 你可以指定 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 }
};
数组的最后一个元素必须是空(NULL). 每个元素包含另一个有 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();
不要忘记当创建上下文的时候初始化JavaScript对象:
JSObject *obj = JSCustomer::JSInit(cx, global);
代码
//main.cpp 演示如何执行javascript
#define XP_PC
#include <string>
#include <iostream>
#include <fstream>
#include <jsapi.h>
#include "JSCustomer.h"
JSClass globalClass = {
"Global",
0,
JS_PropertyStub,
JS_PropertyStub,
JS_PropertyStub,
JS_PropertyStub,
JS_EnumerateStub,
JS_ResolveStub,
JS_ConvertStub,
JS_FinalizeStub
};
void main(int argc, char *argv[])
{
if ( argc < 2 )
{
std::cout << "JSExec usage" << std::endl
<< "------------" << std::endl
<< "JSExec <fileName>" << std::endl;
}
std::string script;
std::string buffer;
std::ifstream istr(argv[1]);
if ( istr.is_open() )
{
do{
std::getline(istr, buffer);
script += buffer;
} while (!istr.fail());
}
else
{
std::cout << "JSExec error" << std::endl
<< "------------" << std::endl
<< "Can't open scriptfile " << argv[1] << std::endl;
exit(0);
}
JSRuntime *rt = JS_Init(1000000L);
if ( rt )
{
JSContext *cx = JS_NewContext(rt, 8192);
if ( cx )
{
JSObject *globalObj = JS_NewObject(cx, &globalClass, 0, 0);
if ( globalObj )
{
JS_InitStandardClasses(cx, globalObj);
// Init JSCustomer
JSCustomer::JSInit(cx, globalObj);
// Execute the script
jsval rval;
uintN lineno = 0;
JSString *str;
JSBool ok = JS_EvaluateScript(cx, globalObj, script.c_str(),
script.length(), argv[1], lineno, &rval);
if ( ok == JS_TRUE )
{
str = JS_ValueToString(cx, rval);
char *s = JS_GetStringBytes(str);
std::cout << "JSExec result" << std::endl
<< "-------------" << std::endl
<< s << std::endl;
}
else
{
std::cout << "JSExec error" << std::endl
<< "------------" << std::endl
<< "Error in JavaScript file " << argv[1] << std::endl;
}
}
else
{
std::cout << "Unable to create the global object";
}
JS_DestroyContext(cx);
}
else
{
std::cout << "Unable to create a context";
}
JS_Finish(rt);
}
else
{
std::cout << "Unable to initialize the JavaScript Engine";
}
}
//JSCustomer.h 演示Customer JavaScript类的定义
/*** JSCustomer.h - Example for my tutorial : Scripting C++ with JavaScript
* (c) 2002 - Franky Braem
* http://www.braem17.yucom.be
*/
#ifndef _JSCustomer_H
#define _JSCustomer_H
#include "Customer.h"
class JSCustomer
{
public:
/*** Constructor*/
JSCustomer()
: m_pCustomer(NULL){}
/*** Destructor*/
virtual ~JSCustomer()
{
delete m_pCustomer;
m_pCustomer = NULL;
}
/*** JSGetProperty - Callback for retrieving properties*/
static JSBool JSGetProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp);
/*** JSSetProperty - Callback for setting properties*/
static JSBool JSSetProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp);
/*** JSConstructor - Callback for when a wxCustomer object is created*/
static JSBool JSConstructor(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
/*** JSDestructor - Callback for when a wxCustomer object is destroyed*/
static void JSDestructor(JSContext *cx, JSObject *obj);
/*** JSInit - Create a prototype for wxCustomer*/
static JSObject* JSInit(JSContext *cx, JSObject *obj, JSObject *proto = NULL);
static JSBool computeReduction(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
static JSClass Customer_class;
void setCustomer(Customer *customer){m_pCustomer = customer; }
Customer* getCustomer() {return m_pCustomer; }
private:
Customer *m_pCustomer;
static JSPropertySpec Customer_properties[];
static JSFunctionSpec Customer_methods[];
enum{name_prop,age_prop};
};
#endif //_JSCustomer_H
//JSCustomer.cpp 演示JSCustomer类的实现
/*** JSCustomer.cpp - Example for my tutorial : Scripting C++ with JavaScript
* (c) 2002 - Franky Braem
* http://www.braem17.yucom.be
*/
#include <string>
#define XP_PC
#include <jsapi.h>
//#include "Customer.h"
#include "JSCustomer.h"
JSPropertySpec JSCustomer::Customer_properties[] = {
{ "name", name_prop, JSPROP_ENUMERATE },
{ "age", age_prop, JSPROP_ENUMERATE },
{ 0 }
};
JSFunctionSpec JSCustomer::Customer_methods[] = {
{ "computeReduction", computeReduction, 1, 0, 0 },
{ 0, 0, 0, 0, 0 }
};
JSClass JSCustomer::Customer_class = {
"Customer",
JSCLASS_HAS_PRIVATE,
JS_PropertyStub,
JS_PropertyStub,
JSCustomer::JSGetProperty,
JSCustomer::JSSetProperty,
JS_EnumerateStub,
JS_ResolveStub,
JS_ConvertStub,
JSCustomer::JSDestructor
};
JSBool JSCustomer::JSGetProperty(JSContext *cx, JSObject *obj, jsval id,jsval *vp)
{
if (JSVAL_IS_INT(id))
{
JSCustomer *p = (JSCustomer *) JS_GetPrivate(cx, obj);
Customer *customer = p->getCustomer();
switch (JSVAL_TO_INT(id))
{
case name_prop:
{
std::string name = customer->GetName();
JSString *str = JS_NewStringCopyN(cx, name.c_str(), name.length());
*vp = STRING_TO_JSVAL(str);
break;
}
case age_prop:
*vp = INT_TO_JSVAL(customer->GetAge());
break;
}
}
return JS_TRUE;
}
JSBool JSCustomer::JSSetProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
{
if (JSVAL_IS_INT(id))
{
JSCustomer *p = (JSCustomer *) JS_GetPrivate(cx, obj);
Customer *customer = p->getCustomer();
switch (JSVAL_TO_INT(id))
{
case name_prop:
{
JSString *str = JS_ValueToString(cx, *vp);
std::string name = JS_GetStringBytes(str);
customer->SetName(name);
break;
}
case age_prop:
customer->SetAge(JSVAL_TO_INT(*vp));
break;
}
}
return JS_TRUE;
}
JSBool JSCustomer::JSConstructor(JSContext *cx, JSObject *obj, uintN argc,
jsval *argv, jsval *rval)
{
JSCustomer *priv = new JSCustomer();
priv->setCustomer(new Customer());
JS_SetPrivate(cx, obj, (void *) priv);
return JS_TRUE;
}
void JSCustomer::JSDestructor(JSContext *cx, JSObject *obj)
{
JSCustomer *priv = (JSCustomer*) JS_GetPrivate(cx, obj);
delete priv;
priv = NULL;
}
JSObject *JSCustomer::JSInit(JSContext *cx, JSObject *obj, JSObject *proto)
{
JSObject *newProtoObj = JS_InitClass(cx, obj, proto,
&Customer_class, JSCustomer::JSConstructor,
0,NULL, JSCustomer::Customer_methods,NULL, NULL);
JS_DefineProperties(cx, newProtoObj, JSCustomer::Customer_properties);
return newProtoObj;
}
JSBool JSCustomer::computeReduction(JSContext *cx, JSObject *obj, uintN argc,
jsval *argv, jsval *rval)
{
JSCustomer *p = (JSCustomer*) JS_GetPrivate(cx, obj);
if ( p->getCustomer()->GetAge() < 25 )
*rval = INT_TO_JSVAL(10);
else
*rval = INT_TO_JSVAL(5);
return JS_TRUE;
}
//Customer.h 演示Customer C++类的定义
#ifndef _Customer_H
#define _Customer_H
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;
};
#endif
//example.js 演示JavaScript的例子
var c = new Customer();
c.name = "Franky";
c.age = 32;
c.computeReduction();