本文档介绍了如何在你的C + +程序中嵌入Mozilla的JavaScript引擎SpiderMonkey的。
JavaScript被广泛用于在浏览器中运行的客户端脚本。但Mozilla的JavaScript引擎是一个库,可以链接到任何C + +程序,不只是一个浏览器。许多应用程序都可以从中受益脚本。这些程序可以执行JavaScript代码,使用C + +的SpiderMonkey的API。
SpiderMonkey的做什么
JavaScript引擎编译和执行脚本包含JavaScript语句和函数。引擎处理执行脚本所需要的对象,内存分配和清理垃圾收集,它不再需要的对象。
SpiderMonkey的支持1.0至1.8的JavaScript语言的版本。 JS1.3及以后符合ECMAScript规范,ECMA262-3。后来的版本也包含Mozilla的扩展功能,如数组领悟和发电机。 SpiderMonkey的还支持E4X(可选)。
这个词的JavaScript可能带来的功能,如喜欢的onclick事件处理程序,DOM对象的window.open和XMLHttpRequest介意。但在Mozilla,所有这些功能实际上是由其他的组件,而不是引擎本身的SpiderMonkey。 SpiderMonkey的提供一些核心的JavaScript数据类型数字,字符串,数组,对象,等上几个方法,如Array.push。这也使得它为每个应用程序容易暴露其自身的一些对象和函数JavaScript代码。浏览器DOM对象暴露。你的应用程序会暴露你想要写的那种脚本相关的对象。它是应用程序开发人员决定哪些对象和方法的脚本公开。
世界,你好
使用的SpiderMonkey库
你的应用程序可以像任何其他的C + +库使用的SpiderMonkey。要建立从源代码的SpiderMonkey,请参见SpiderMonkey的构建文档。你可以建立的SpiderMonkey为静态库或共享库。
某些平台(如Debian Linux的)提供的SpiderMonkey作为一个预编译包,这样可以更容易安装,但难以调试。 XULRunner的SDK还包含了预建的SpiderMonkey的和相应的头文件和导入库。
C + +代码访问的SpiderMonkey通过JSAPI,,包括头的“jsapi.h”。 JSAPI提供设立的JavaScript运行时,编译和执行脚本,创建和检查JavaScript数据结构,错误处理,使安全检查和调试脚本功能。
JSAPI功能的概述如下。欲了解更多详细信息,请参阅的JSAPI参考。
SpiderMonkey的宇宙
为了运行任何在SpiderMonkey JavaScript代码,应用程序必须有三个关键要素:一个JSRuntime,一个JSContext,和一个全局对象。本节介绍这些东西是什么。下一节将说明如何设置,使用JSAPI功能。
运行时间。 àJSRuntime或运行时,是JavaScript变量,对象,脚本,和您的应用程序所使用的上下文被分配在这个空间里。每的JSContext和应用程序中的每个对象住在JSRuntime的。他们无法前往其他运行时或运行时之间共享。大多数应用程序只需要一个运行。
上下文。 ÂJSContext,或上下文,像一个小机器,可以做很多事情,涉及的JavaScript代码和对象。它可以编译和执行脚本,获取和设置对象属性,调用JavaScript函数,转换JavaScript数据从一种类型到另一个,创建对象,依此类推。几乎所有的的JSAPI功能需要一个JSContext *作为第一个参数,就像最<stdio.h>中的函数需要一个FILE *。
上下文和线程之间存在着密切的关联。很简单,单线程的应用程序可以使用单个上下文中的一切。但每个上下文只能做一件事的时间,所以在多线程应用程序,只有一个线程在同一时间使用任何给定上下文。这样的应用程序通常有一个每线程上下文。 JavaScript对象,另一方面,没有永久关联的脚本,线程或上下文创建它们。许多脚本甚至是多个线程之间可以共享,如下图所示。
图1.1说明了脚本运行时,上下文和对象的关系。
Figure 1.1
全局对象。最后,全局对象包含的所有类,函数和变量可用于JavaScript代码来使用。每当JavaScript代码类似的window.open(“http://www.mozilla.org/”),它是访问一个全球性的属性,在这种情况下,窗口。 JSAPI应用程序都要有全局属性的脚本可以看到的完全控制权。应用程序启动时创建一个对象,并填充它与标准JavaScript类,如数组和对象。然后添加任何自定义的类,函数和变量(如窗口)应用程序要提供;见下面自定义对象。每次应用程序运行一个JS脚本(例如,使用JS_EvaluateScript),它提供了该脚本使用的全局对象。脚本运行时,它可以创建其自己的全局函数和变量。所有这些函数,类,变量存储全局对象的属性。
一个最小的例子
在上一节所描述的三个关键要素,需要几个JSAPI电话:
运行时:使用JS_NewRuntime,创建和JS_DestroyRuntime把它清理干净,当你做。当您的应用程序的SpiderMonkey共完成,使用JS_ShutDown释放任何剩余高速缓存资源。 (这是一个单纯的礼节,如果进程即将退出反正,但因为这是情况并非总是如此,调用JS_Shutdown是进入一个良好的习惯。)
背景情况:使用JS_NewContext JS_DestroyContext。为了获得最大的ECMAScript标准规范,应用程序也应该使用JS_SetOptions启用JSOPTION_VAROBJFIX。要获得最新的JavaScript语言特性,应用程序可以使用的JS_SetVersion。错误报告也是每个上下文已启用使用JS_SetErrorReporter的。
全局对象:要创建这个对象,你首先需要一个JSClass与JSCLASS_GLOBAL_FLAGS选项。下面的例子定义一个非常基本JSClass(名为global_class)的,没有它自己的方法或属性。使用JS_NewGlobalObject创建一个全局对象。使用JS_InitStandardClasses标准JavaScript全局来填充它。
这可能看起来像一个简单的应用了很多件。它相当于约50行代码,如下图所示。但JSAPI设计扩展到应用程序需要多线程,很多情况下,许多全球性的对象。这是一个细粒度的API,支持多种不同的零件组合,从而使应用程序SpiderMonkey的是如何行为的精确控制。
这里是JSAPI应用最小的所需的样板代码。它包含了上述的一切。
1 /* 2 * This define is for Windows only, it is a work-around for bug 661663. 3 */ 4 #ifdef _MSC_VER 5 # define XP_WIN 6 #endif 7 8 #include "jsapi.h" 9 10 /* The class of the global object. */ 11 static JSClass global_class = { "global", JSCLASS_GLOBAL_FLAGS, JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub, JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, NULL, JSCLASS_NO_OPTIONAL_MEMBERS }; 12 13 /* The error reporter callback. */ 14 void reportError(JSContext *cx, const char *message, JSErrorReport *report) { 15 fprintf(stderr, "%s:%u:%s\n", 16 report->filename ? report->filename : "<no filename="">", 17 (unsigned int) report->lineno, 18 message); 19 } 20 21 int main(int argc, const char *argv[]) { 22 /* JS variables. */ 23 JSRuntime *rt; 24 JSContext *cx; 25 JSObject *global; 26 27 /* Create a JS runtime. */ 28 rt = JS_NewRuntime(8L * 1024L * 1024L); 29 if (rt == NULL) 30 return 1; 31 32 /* Create a context. */ 33 cx = JS_NewContext(rt, 8192); 34 if (cx == NULL) 35 return 1; 36 JS_SetOptions(cx, JSOPTION_VAROBJFIX | JSOPTION_METHODJIT); 37 JS_SetVersion(cx, JSVERSION_LATEST); 38 JS_SetErrorReporter(cx, reportError); 39 40 /* Create the global object in a new compartment. */ 41 global = JS_NewGlobalObject(cx, &global_class, NULL); 42 if (global == NULL) 43 return 1; 44 45 /* Populate the global object with the standard globals, like Object and Array. */ 46 if (!JS_InitStandardClasses(cx, global)) 47 return 1; 48 49 /* Your application code here. This may include JSAPI calls to create your own custom JS objects and run scripts. */ 50 51 JS_DestroyContext(cx); 52 JS_DestroyRuntime(rt); 53 JS_ShutDown(); 54 return 0; 55 }
一个典型的JSAPI应用程序也应该定期执行垃圾收集。应用程序可以做到这一点通过定期调用JS_MaybeGC。
多线程应用程序有额外的基本要求,包括构建要求。见JS_THREADSAFE。
原住民功能
其余的大部分本指南介绍了如何公开自定义功能的脚本。最简单的方式开始露出一个全球性的原生功能。
原住民功能在C + +实现的,但可以从JavaScript调用,就像任何其他的功能。本机函数的签名必须匹配JSNative的。
1 #include <stdlib.h> 2 #include <time.h> 3 #include "jsapi.h" 4 5 /* A simple JS wrapper for the rand() function, from the C standard library. 6 This example shows how to return a number from a JSNative. 7 This is nearly the simplest possible JSNative function. */ 8 JSBool myjs_rand(JSContext *cx, uintN argc, jsval *vp) 9 { 10 JS_SET_RVAL(cx, vp, DOUBLE_TO_JSVAL(rand())); 11 return JS_TRUE; 12 } 13 14 /* A wrapper for the srand() function, from the C standard library. 15 This example shows how to handle optional arguments. */ 16 JSBool myjs_srand(JSContext *cx, uintN argc, jsval *vp) 17 { 18 uint32 seed; 19 20 if (!JS_ConvertArguments(cx, argc, JS_ARGV(cx, vp), "/u", &seed)) 21 return JS_FALSE; 22 23 /* If called with no arguments, use the current time as the seed. */ 24 if (argc == 0) 25 seed = time(NULL); 26 27 srand(seed); 28 29 JS_SET_RVAL(cx, vp, JSVAL_VOID); /* return undefined */ 30 return JS_TRUE; 31 } 32 33 /* A wrapper for the system() function, from the C standard library. 34 This example shows how to report errors. */ 35 JSBool myjs_system(JSContext *cx, uintN argc, jsval *vp) 36 { 37 JSString* str; 38 char *cmd; 39 int rc; 40 41 if (!JS_ConvertArguments(cx, argc, JS_ARGV(cx, vp), "s", &str)) 42 return JS_FALSE; 43 44 cmd = JS_EncodeString(cx, str); 45 rc = system(cmd); 46 JS_free(cx, cmd); 47 if (rc != 0) { 48 /* Throw a JavaScript exception. */ 49 JS_ReportError(cx, "Command failed with exit code %d", rc); 50 return JS_FALSE; 51 } 52 53 JS_SET_RVAL(cx, vp, JSVAL_VOID); /* return undefined */ 54 return JS_TRUE; 55 }
每个JSNative具有相同的签名,无论什么样的参数,预计将收到从JavaScript。
ARGC和VP的JavaScript功能参数中给出。 ARGC告诉调用者传递多少实际参数,和JS_ARGV(CX,VP)返回一个数组,这些参数。参数不具有本地C + +类型,如int和float,相反,它们是jsvals,JavaScript的值。本机函数使用JS_ConvertArguments C + +类型转换的参数,并将它们存储在局部变量。本机函数使用JS_SET_RVAL(CX,副总裁,VAL)来存储它的JavaScript返回值。
成功,的JSNative必须致电JS_SET_RVAL的和返回JS_TRUE的。返回的值传递给JS_SET_RVAL的JavaScript调用者。
一旦出故障,JSNative调用一个错误报告功能,在这种情况JS_ReportError,并返回JS_FALSE的。这会导致一个JavaScript的异常被抛出。来电者可以使用JavaScript try / catch语句来捕获异常。
为了使从JavaScript调用原生函数,声明一个表的JSFunctionSpecs描述的功能。然后调用JS_DefineFunctions。
1 static JSFunctionSpec myjs_global_functions[] = { 2 JS_FS("rand", myjs_rand, 0, 0), 3 JS_FS("srand", myjs_srand, 0, 0), 4 JS_FS("system", myjs_system, 1, 0), 5 JS_FS_END 6 }; 7 8 ... 9 if (!JS_DefineFunctions(cx, global, myjs_global_functions)) 10 return JS_FALSE; 11 ...
一旦功能被定义在全局任何使用全局全局对象的脚本可以打电话给他们,就像任何网页可以调用警报。在我们已经创造的环境中,“世界你好”的剧本看起来像这样:
1 system("echo hello world");
JSAPI概念
本节旨在填补JSAPI到目前为止在画面的主要差距。做任何有用的SpiderMonkey,您必须阅读所有三个部分。
JavaScript的值
主要文章:指向jsval
JavaScript是一种动态类型的语言:变量和属性没有固定的类型在编译的时候。怎么能一个静态类型的语言,如C或C+ +,在所有变量类型,用JavaScript交互? JSAPI提供一种数据类型,指向jsval,它可以包含JavaScript的任何类型的值。一个JSVal可以是一个数字,字符串,布尔值,一个对象的引用(如对象,数组,日期,或功能),或null或未定义的特殊价值之一。
对于整数和布尔值中,一个JSVal包含本身的价值。在其他情况下,指向jsval是一个指针,指向一个对象,字符串或数字。
的JSAPI包括宏测试的JavaScript数据类型的一个JSVal。它们是:
JSVAL_IS_OBJECT
JSVAL_IS_NUMBER
JSVAL_IS_INT
JSVAL_IS_DOUBLE
JSVAL_IS_STRING
JSVAL_IS_BOOLEAN
JSVAL_IS_NULL
JSVAL_IS_VOID
如果一个JSVal点JSObject,jsdouble或JSString你可以投JSVal中,其基本数据类型使用JSVAL_TO_OBJECT JSVAL_TO_DOUBLE和JSVAL_TO_STRING分别。在某些情况下,您的应用程序或JSAPI函数需要一个特定的数据类型,而不是一个JSVal一个变量或参数,这是非常有用的。同样,你可以蒙上了一层的JSObject jsdouble,或JSString指针到使用OBJECT_TO_JSVAL,DOUBLE_TO_JSVAL,或STRING_TO_JSVAL的一个JSVal。
垃圾收集
当它运行时,JavaScript代码隐式分配内存的对象,字符串,变量,依此类推。垃圾收集的过程是由JavaScript引擎检测时,那些记忆的碎片不再可达,他们不能永远可能被再次使用和回收内存。
垃圾收集JSAPI应用有两个重要的后果。首先,应用程序必须非常小心,以确保它需要的任何值可达GC。垃圾收集关于它的工作是相当渴望。任何对象,你离开躺在身边将被摧毁,如果你不告诉JSAPI你还在使用它。其次,应用程序应该采取措施来减少对性能的影响垃圾收集。
保持活着的对象
如果您JSAPI应用程序崩溃,它可能是由于一个GC相关的错误。应用程序必须确保垃圾收集器可以达到仍在使用的所有的对象,数字和字符串。否则,GC会释放占用的内存由这些值,从而导致你的程序尝试使用他们的下一次可能的崩溃。
有很多种方法,以确保值可达GC。
如果你只需要值保持的持续时间可达一个JSNative调用,将其存储在* RVAL或argv数组中的元素。在这些位置中存储的值永远可达。为了获得额外的argv插槽,使用JSFunctionSpec.extra。
如果自定义对象需要一定的值保留在内存中,只是存储的值对象的属性。只要该对象是可到达的,其属性将保持可达。如果这些值必须不从JavaScript访问,使用预留插槽代替。值或存放在私有数据,,并实施JSClass.mark。
如果一个函数创建新的对象,字符串或数字,它,可以使用JS_EnterLocalRootScope JS_LeaveLocalRootScope保持这些值活着的持续时间的功能。
要保持一个值永久地活着,将其存储在GC根。
尽管如此,GC的错误发生。这两个功能,仅适用于调试版本是特别有用的调试GC相关的崩溃:
使用JS_SetGCZeal启用额外的垃圾收集。 GC热情通常会导致发生更快(接近其原因),更可靠的GC相关的崩溃。只有开发和调试,使得JS很慢,因为额外的垃圾收集。
使用JS_DumpHeap转储的SpiderMonkey的堆或特定的有趣的部分。
见的SpiderMonkey的垃圾收集更多的细节提示。
GC性能
过于频繁的垃圾收集是一个性能问题。某些应用程序可以减少垃圾回收的频率简单地通过增加的初始大小JSRuntime。
也许最好的方法是执行垃圾收集,在空闲时间,当它是最有可能对用户产生任何影响。默认情况下,JavaScript引擎执行垃圾收集时,它没有别的选择,但成长的过程。这意味着,垃圾收集通常发生内存密集型代码运行时,也许最坏的时间。应用程序可以在更方便的时间通过调用JS_GC或JS_MaybeGC引发垃圾收集。 JS_GC势力的垃圾收集。 JS_MaybeGC执行垃圾回收仅当它是一个有价值的内存量可能收回。
错误和异常
JSAPI函数检查返回值的重要性,当然也自不待言。几乎每JSAPI函数,它接受一个JSContext *参数可以失败。该系统可能会耗尽内存。有可能是一个脚本中的语法错误。或脚本可能会明确地抛出一个异常。
JavaScript语言有例外,和C + +有例外,但它们是不一样的东西。 SpiderMonkey中不使用任何C + +异常。 JSAPI函数从不抛出C + +异常,SpiderMonkey的调用应用程序回调时,回调必须不抛出C + +异常。
抛出和捕获异常
我们已经看到的一个例子如何抛出异常从JSNative功能的。只需致电JS_ReportError,printf风格的论据,返回JS_FALSE。
1 rc = system(cmd); 2 if (rc != 0) { 3 /* Throw a JavaScript exception. */ 4 JS_ReportError(cx, "Command failed with exit code %d", rc); 5 return JS_FALSE; 6 }
这是非常像的JavaScript语句抛出新的错误(“命令执行失败,退出代码”+ RC);。此外还要注意,调用JS_ReportError并不会造成一个C + +异常被抛出。它只是创建一个新的JavaScript错误对象,并将其存储在上下文作为当前挂起的异常。该应用程序还必须返回JS_FALSE的。
一旦C + +函数返回JS_FALSE,JavaScript引擎开始解除JavaScript堆栈,寻找一个catch或finally块来执行。但SpiderMonkey的堆栈展开从来没有从堆栈中删除应用程序的C + +函数。相反,SpiderMonkey的简单的应用程序,它可以处理的错误,因为它选择或只是返回JS_FALSE的让它进一步传播了堆栈返回JS_FALSE或NULL。
抛出和捕获异常的几个例子,可以发现在JSAPI短语。
错误报告
TODO您的自定义errorreporter
TODO时报告错误
未捕获异常的自动处理
* JS_Compile JS_Call *,JS_Execute *,*功能自动JS_Evaluate错误在某些情况下,记者通过例外。这些功能检查,只是在返回之前,看看是否有异常等待处理在当前JSContext。如果是这样,然后检查,看看是否有任何其他的JavaScript脚本或函数在栈上,JSContext。如果是这样的话,那么该异常可能尚未被抓到,所以SpiderMonkey的什么都不做,回报JS_FALSE,允许例外传播。但是如果没有什么是JavaScript堆栈,然后通过未捕获的异常错误记者和清除挂起例外。
基本的后果是最上层的应用程序代码,就可以设置一个错误的记者,并开始调用JSAPI函数。它永远不会有明确处理未捕获的异常错误记者被自动调用。应用程序可以禁用自动未捕获异常处理使用JSOPTION_DONT_REPORT_UNCAUGHT选项,但必须再处理未捕获异常调用JS_GetPendingException JS_IsExceptionPending,JS_ReportPendingException,和/或JS_ClearPendingException JSAPI函数返回JS_FALSE的的或NULL每当明确。
栋笃神探错误
一个报告错误的JSNative回调的另一种方式是这样的:
1 if (p == NULL) { 2 JS_ReportOutOfMemory(cx); 3 return JS_FALSE; 4 }
这确实巧妙地不同什么JS_ReportError的东西。
大多数错误,包括那些提出JS_ReportError,表示为JavaScript异常,并因此与JavaScript的异常处理语言功能,尝试,捕捉,终于。然而,在某些情况下,我们不希望脚本能够捕捉错误,我们希望马上终止脚本执行。如果系统内存在中间的脚本,我们不希望finally块来执行,因为几乎任何一个脚本至少需要一点点的记忆,而我们没有。如果脚本已运行时间太长,我们要杀死它,它没有好抛出一个异常脚本可能只是抓住它,并保持持续。
,因此JS_ReportOutOfMemory(CX)不设置挂起异常。是抓不到的错误。
如果SpiderMonkey的运行内存,或JSAPI回调返回JS_FALSE一个异常等待的情况下,这将被视为一个不可捕获的错误。 JavaScript堆栈以正常的方式平仓,惟该catch和finally块将被忽略。 JSAPI最近调用应用返回JS_FALSE,或NULL。
一个未捕获的错误离开JSContext的在一个良好的状态。它可以马上再次使用。应用程序没有做任何事情来“恢复”的错误,JSAPI。 (当然,如果错误是,该系统内存,即要处理的问题仍然存在。)
下面是一些示例代码抛出一个未捕获的错误。
1 /* Call the error reporter, if any. This part is optional. */ 2 JS_ReportError(cx, "The server room is on fire!"); 3 JS_ReportPendingException(cx); 4 5 /* Make sure the error is uncatchable. */ 6 JS_ClearPendingException(cx); 7 return JS_FALSE;
更多示例代码
下面的例子说明如何实现使用JSAPI几个不同的效果。
请注意,最重要的例子是在“一个最小的例子”一节所述。更多JSAPI代码样本中出现JSAPI短语。
定义对象和属性
1 /* Statically initialize a class to make "one-off" objects. */ 2 JSClass my_class = { 3 "MyClass", 4 5 /* All of these can be replaced with the corresponding JS_*Stub 6 function pointers. */ 7 my_addProperty, my_delProperty, my_getProperty, my_setProperty, 8 my_enumerate, my_resolve, my_convert, my_finalize 9 }; 10 11 JSObject *obj; 12 13 /* 14 * Define an object named in the global scope that can be enumerated by 15 * for/in loops. The parent object is passed as the second argument, as 16 * with all other API calls that take an object/name pair. The prototype 17 * passed in is null, so the default object prototype will be used. 18 */ 19 obj = JS_DefineObject(cx, globalObj, "myObject", &my_class, NULL, 20 JSPROP_ENUMERATE); 21 22 /* 23 * Define a bunch of properties with a JSPropertySpec array statically 24 * initialized and terminated with a null-name entry. Besides its name, 25 * each property has a "tiny" identifier (MY_COLOR, e.g.) that can be used 26 * in switch statements (in a common my_getProperty function, for example). 27 */ 28 enum my_tinyid { 29 MY_COLOR, MY_HEIGHT, MY_WIDTH, MY_FUNNY, MY_ARRAY, MY_RDONLY 30 }; 31 32 static JSPropertySpec my_props[] = { 33 {"color", MY_COLOR, JSPROP_ENUMERATE}, 34 {"height", MY_HEIGHT, JSPROP_ENUMERATE}, 35 {"width", MY_WIDTH, JSPROP_ENUMERATE}, 36 {"funny", MY_FUNNY, JSPROP_ENUMERATE}, 37 {"array", MY_ARRAY, JSPROP_ENUMERATE}, 38 {"rdonly", MY_RDONLY, JSPROP_READONLY}, 39 {0} 40 }; 41 42 JS_DefineProperties(cx, obj, my_props); 43 44 /* 45 * Given the above definitions and call to JS_DefineProperties, obj will 46 * need this sort of "getter" method in its class (my_class, above). See 47 * the example for the "It" class in js.c. 48 */ 49 static JSBool 50 my_getProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) 51 { 52 if (JSVAL_IS_INT(id)) { 53 switch (JSVAL_TO_INT(id)) { 54 case MY_COLOR: *vp = . . .; break; 55 case MY_HEIGHT: *vp = . . .; break; 56 case MY_WIDTH: *vp = . . .; break; 57 case MY_FUNNY: *vp = . . .; break; 58 case MY_ARRAY: *vp = . . .; break; 59 case MY_RDONLY: *vp = . . .; break; 60 } 61 } 62 return JS_TRUE; 63 }
定义类
这拉在一起上述定义一个构造函数的原型对象的原型的构造和性能,一个API调用的API元素。
初始化一个类定义它的构造函数的原型,和每个实例和每类属性。后者被称为“静态”,以下类推到Java。它们被定义的构造函数对象的范围中,所以随着新MyClass的(),MyClass.myStaticProp工作。
JS_InitClass需要很多参数,但如果没有这样的属性或方法,你可以传递NULL任何在过去四年。
请注意,您不需要调用JS_InitClass,做出的一个新实例类,否则将是一个鸡和蛋的问题是全局对象,但如果你需要调用一个构造函数的脚本作者,你应该叫JS_InitClass通过新的和/或作者类原型对象(MyClass.prototype),为扩展新的属性在运行时。在一般情况下,如果你想支持多实例,分享行为,使用JS_InitClass。
1 protoObj = JS_InitClass(cx, globalObj, NULL, &my_class, 2 3 /* native constructor function and min arg count */ 4 MyClass, 0, 5 6 /* prototype object properties and methods -- these 7 will be "inherited" by all instances through 8 delegation up the instance's prototype link. */ 9 my_props, my_methods, 10 11 /* class constructor properties and methods */ 12 my_static_props, my_static_methods);
1 Running scripts 2 3 /* These should indicate source location for diagnostics. */ 4 char *filename; 5 uintN lineno; 6 7 /* 8 * The return value comes back here -- if it could be a GC thing, you must 9 * add it to the GC's "root set" with JS_AddRoot(cx, &thing) where thing 10 * is a JSString *, JSObject *, or jsdouble *, and remove the root before 11 * rval goes out of scope, or when rval is no longer needed. 12 */ 13 jsval rval; 14 JSBool ok; 15 16 /* 17 * Some example source in a C string. Larger, non-null-terminated buffers 18 * can be used, if you pass the buffer length to JS_EvaluateScript. 19 */ 20 char *source = "x * f(y)"; 21 22 ok = JS_EvaluateScript(cx, globalObj, source, strlen(source), 23 filename, lineno, &rval); 24 25 if (ok) { 26 /* Should get a number back from the example source. */ 27 jsdouble d; 28 29 ok = JS_ValueToNumber(cx, rval, &d); 30 . . . 31 } 32 Calling functions 33 34 /* Call a global function named "foo" that takes no arguments. */ 35 ok = JS_CallFunctionName(cx, globalObj, "foo", 0, 0, &rval); 36 37 jsval argv[2]; 38 39 /* Call a function in obj's scope named "method", passing two arguments. */ 40 argv[0] = . . .; 41 argv[1] = . . .; 42 ok = JS_CallFunctionName(cx, obj, "method", 2, argv, &rval);
JSContext
因为有一定量的分配和维护上下文相关联的开销,JSAPI应用程序应:
仅创建尽可能多的背景下,因为它需要在同一时间。
只要他们可能需要查看上下文,而不是破坏并根据需要重新创建它们。
如果您的应用程序创建多个运行时,应用程序可能需要知道一个上下文相关联的运行。在这种情况下,使用JS_GetRuntime。
使用JS_SetContextPrivate JS_GetContextPrivate与上下文关联的特定于应用程序的数据。
初始化内置的和全局的JS对象
对于内置对象的SpiderMonkey提供的完整列表,请参阅JS_InitStandardClasses。
应用程序提供脚本的全局对象,在很大程度上决定这些脚本可以做什么。例如,Firefox浏览器使用其自身的全局对象,窗口。要改变你的应用程序的全局对象,调用JS_SetGlobalObject。
自定义对象的创建和初始化
除了使用引擎的内置对象,您将创建,初始化,并使用自己的JS对象。这是尤其如此,如果你正在使用脚本来自动化你的应用程序的JS引擎。自定义JS对象直接提供节目服务,也可以作为你的程序的服务接口。例如,一个自定义的JS提供直接服务的对象可能是一个能够处理所有的应用程序的网络访问,或可能作为一个中介经纪人的数据库服务。或者使用JS对象的镜像中的应用已经存在的数据和功能,可提供一个面向对象的接口为C代码,是不是,否则,严格讲,面向对象本身。这种自定义对象作为一个接口,应用程序本身,从应用程序的值传递给用户,并接收和处理用户的输入,然后返回到应用程序。提供访问控制的相关功能的应用,还可以使用这样一个对象。
创建自定义对象的JS引擎可以使用的方法有两种:
写一个JS脚本创建一个对象,其属性,方法,构造函数,然后通过脚本给JS引擎在运行时。
代码嵌入在应用程序中定义对象的属性和方法,调用引擎来初始化一个新的对象,然后通过额外的引擎调用该对象的属性设置。这种方法的优点是,你的应用程序可以包含本地方法直接操纵的对象嵌入。
在这两种情况下,如果你创建了一个对象,然后希望它坚持在运行时,它可以使用其他脚本,你必须铲除的对象通过调用JS_AddRoot或JS_AddNamedRoot。使用这些功能可以确保,JS引擎将跟踪对象,并清除它们在垃圾收集,如果合适。
从脚本创建一个对象
从脚本创建一个自定义的JS对象的原因之一是,当你只需要一个对象存在,因为只要使用它正在执行的脚本。创建对象,坚持跨脚本调用,你可以嵌入对象的代码,在你的应用程序,而不是使用脚本。
注:您也可以使用脚本来创建持久化对象,也。
要使用脚本来创建自定义对象:
定义和规范的对象。它是什么打算做什么?其数据成员(属性)是什么?其方法(函数)是什么?是否需要运行时构造函数?
代码的JS脚本,定义和创建对象。例如:函数MYFUN(){VAR = NEWOBJECT();。 。 。 }注意:使用JavaScript对象的脚本外发生的背景下,在你的应用程序中嵌入JS引擎。有关对象的脚本欲了解更多信息,请参阅客户端JavaScript指南和服务器端JavaScript。嵌入适当的在您的应用程序调用的JS引擎(S)来编译和执行脚本。你有两个选择:1)编译和执行一个单一的脚本调用,JS_EvaluateUCScript JS_EvaluateScript或2)。一次调用JS_CompileScript或JS_CompileUCScript编译脚本,然后执行它反复与JS_ExecuteScript个别呼叫。这些呼叫的“UC”版本提供支持Unicode编码的脚本。
使用脚本创建一个对象,你不仅可以提供仅在一生的脚本,或者可以创建坚持脚本执行完成后。通常情况下,一旦脚本执行完成后,其对象被销毁。在许多情况下,这种行为正是你的应用需要。然而,在另一些情况下,你会想跨脚本,或者您的应用程序的生命周期的对象持久化。在这些情况下,你需要在你的应用程序代码直接嵌入对象的创建,或你需要配合的对象直接全局对象,因此,它仍然存在,只要全局对象本身仍然存在。
自定义对象
应用程序可以创建一个自定义对象而没有用JSClass:
实施的getter,setter方法,方法为您的自定义对象的C或C + +。写为每个getter或setter JSPropertyOp。写一个每个方法或JSFastNative JSNative,。
声明数组,包含一个JSPropertySpec您的自定义对象的属性信息,包括getter和setter方法。
声明一个JSFunctionSpec的数组,其中包含信息关于您的自定义对象的方法。
呼叫JS_NewObject,JS_ConstructObject或JS_DefineObject的创建对象。
调用JS_DefineProperties来定义对象的属性。
调用JS_DefineFunctions定义对象的方法。
JS_SetProperty也可以被用来创建一个对象的属性。它创建的属性没有getter或setter方法,它们是普通的JavaScript属性。
提供的专用数据的对象
喜欢的背景下,您可以将大量的数据与对象,而无需将数据存储在对象本身。呼叫JS_SetPrivate建立为对象的私有数据的指针,并调用JS_GetPrivate检索指针,这样就可以访问数据。你的应用程序是负责创建和管理这个可选的私有数据。
要创建私有数据和它关联的对象:
建立私有数据,你将一个普通的C void指针变量。
呼叫JS_SetPrivate,建立私有数据指定的对象,并指定指向数据的指针。
例如:
1
|
JS_SetPrivate(cx, obj, pdata);
|
在以后的时间来检索数据,请致电JS_GetPrivate,并作为一个参数传递的对象。这个函数返回一个对象的私有数据指针:
1 pdata = JS_GetPrivate(cx, obj);
特别专题
Unicode的
要通过JavaScript和本地代码之间的Unicode数据,代表UTF-16在内存中的数据。 JavaScript字符串,属性名和程序都做了,这是16位无符号整数的jschars。
许多的JSAPI功能操作null结尾的8位字符的字符串。这些函数将char *的参数由零到16位的字符串,每8位扩展到16位字符,除非JS_C_STRINGS_ARE_UTF8定义或JS_SetCStringsAreUTF8被调用,在这种情况下,每个char *字符串被解释为UTF-8 Unicode文本。
JSAPI提供基于jschar的版本,许多API操作字符串的函数,对象的属性,JavaScript代码。
基于字符函数基于jschar的功能
Unicode数据
的过时JS_GetStringBytes自JavaScript 1.8.5的JS_GetStringChars
JS_NewString JS_NewUCString
JS_NewStringCopyN JS_NewUCStringCopyN
JS_NewStringCopyZ JS_NewUCStringCopyZ
JS_InternString JS_InternUCString,JS_InternUCStringN
JS_ReportErrorNumber JS_ReportErrorNumberUC
JS_ReportErrorFlagsAndNumber JS_ReportErrorFlagsAndNumberUC
Unicode属性名
JS_DefineProperty JS_DefineUCProperty
JS_DefinePropertyWithTinyId JS_DefineUCPropertyWithTinyId
JS_DefineFunction JS_DefineUCFunction
JS_HasProperty JS_HasUCProperty
JS_LookupProperty JS_LookupUCProperty
JS_GetProperty JS_GetUCProperty
JS_GetPropertyAttributes JS_GetUCPropertyAttributes
JS_GetPropertyAttrsGetterAndSetter JS_GetUCPropertyAttrsGetterAndSetter
JS_SetProperty JS_SetUCProperty
JS_SetPropertyAttributes JS_SetUCPropertyAttributes
JS_DeleteProperty2 JS_DeleteUCProperty2
JS_AlreadyHasOwnProperty JS_AlreadyHasOwnUCProperty
Unicode的JavaScript源
JS_CompileScript JS_CompileUCScript
JS_CompileScriptForPrincipals JS_CompileUCScriptForPrincipals
JS_CompileFunction JS_CompileUCFunction
JS_CompileFunctionForPrincipals JS_CompileUCFunctionForPrincipals
JS_EvaluateScript JS_EvaluateUCScript
JS_EvaluateScriptForPrincipals JS_EvaluateUCScriptForPrincipals
功能基于jschar的工作一模一样基于字符的同名,除了传统的职能采取一个char *参数的Unicode的版本采取jschar *参数,通常用指定的长度中的字符串jschars的单独的参数。
编译脚本
运行一个脚本,最简单的方法是使用JS_EvaluateScript,编译并执行该脚本一气呵成。
但有时应用程序需要多次运行一个脚本。在这种情况下,它可能会更快一次编译脚本,并执行它多次。
JSAPI提供一个的类型,JSScript,代表已编译的脚本。一个JSScript的生命周期看起来像这样:
一些JavaScript代码的应用程序编译,使用JS_CompileScript JS_CompileUTF8File或JS_CompileFileHandle的。这些函数返回一个指针到一个新JSScript。
该应用程序调用JS_ExecuteScript的(还是JS_ExecuteScriptPart)任意次数。使用一个JSScript在多个不同的背景和不同的全局对象,它是安全的,但仅限于在它被创建的的JSRuntime和线程。
下面是一些示例代码,使用编译的脚本:
1 /* 2 * Compile a script and execute it repeatedly until an 3 * error occurs. (If this ever returns, it returns false. 4 * If there's no error it just keeps going.) 5 */ 6 JSBool compileAndRepeat(JSContext *cx, const char *filename) 7 { 8 JSScript *script; 9 10 script = JS_CompileUTF8File(cx, JS_GetGlobalObject(cx), filename); 11 if (script == NULL) 12 return JS_FALSE; /* compilation error */ 13 14 for (;;) { 15 jsval result; 16 17 if (!JS_ExecuteScript(cx, JS_GetGlobalObject(cx), script, &result)) 18 break; 19 JS_MaybeGC(cx); 20 } 21 22 return JS_FALSE; 23 }
一生的编译脚本绑到一个JavaScript对象的生命周期,垃圾收集器会破坏脚本的时候它不再是可达。 JSAPI提供此功能通过JS_NewScriptObject功能。使用此功能的脚本的生命周期是这样的:
应用程序编译一些JavaScript代码。
为了保护从垃圾收集编译的脚本,应用程序创建一个已编译的的脚本对象通过调用JS_NewScriptObject,使得该对象GC可达使用JS_SetReservedSlot JS_SetProperty,JS_AddRoot或其他一些功能。
执行应用程序的编译后的脚本任意次数。
随着应用程序的发展,最终它并不需要再编译脚本,编译脚本对象变得不可达的。
垃圾收集器,然后最终收集遥不可及的脚本及其组件。
下面是示例代码演示的技术,但要注意,这种情况下是不是真的复杂,需要使用JS_NewScriptObject。上面的例子做同样的事情,更直接。
1 /* 2 * Compile a script and execute it repeatedly until an 3 * error occurs. (If this ever returns, it returns false. 4 * If there's no error it just keeps going.) 5 */ 6 JSBool compileAndRepeat(JSContext *cx, const char *filename) 7 { 8 JSScript *script; 9 JSObject *scriptObj; 10 11 script = JS_CompileUTF8File(cx, JS_GetGlobalObject(cx), filename); 12 if (script == NULL) 13 return JS_FALSE; /* compilation error */ 14 15 scriptObj = JS_NewScriptObject(cx, script); 16 if (scriptObj == NULL) { 17 JS_DestroyScript(cx, script); 18 return JS_FALSE; 19 } 20 21 if (!JS_AddNamedObjectRoot(cx, &scriptObj, "compileAndRepeat script object")) 22 return JS_FALSE; 23 24 for (;;) { 25 jsval result; 26 27 if (!JS_ExecuteScript(cx, JS_GetGlobalObject(cx), script, &result)) 28 break; 29 JS_MaybeGC(cx); 30 } 31 32 JS_RemoveObjectRoot(cx, &scriptObj); /* scriptObj becomes unreachable 33 and will eventually be collected. */ 34 return JS_FALSE; 35 }
安全
许多应用程序使用SpiderMonkey的运行不受信任的代码。在这种设计的应用,重要的是想通过提前的安全关切。你的应用程序将需要做几件事情。
部署安全更新 - Firefox会自动安装更新,因此,尽快为他们提供安全修补程序部署。除非你也定期部署SpiderMonkey的安全更新,可以使用一个确定的黑客公开已知的错误在发动机来攻击你的应用程序。请注意,我们这里谈论的是什么样的攻击,黑客使用JavaScript来攻击C + +代码的引擎本身(或嵌入)。其余的项目在此列表中谈在JavaScript本身产生的安全问题,即使发动机正常工作的。
阻止简单的拒绝服务攻击 - 这样的程序,而(真){}不应该挂您的应用程序。要停止执行脚本运行时间过长,使用JS_SetOperationCallback。同样,类似的函数函数f(){();}不应您的应用程序堆栈溢出崩溃。要阻止,使用JS_SetNativeStackQuota。
控制对敏感数据的访问 - 你的应用程序的数据可能会暴露一些脚本,其他脚本不应该能够看到。例如,你可以让你的客户编写自己的数据上运行的脚本,但没有其他客户的数据。必须以某种方式执行这些访问规则。
控制访问危险的功能 - 假设应用程序有一个方法deleteUserAccount,()是只由系统管理员可以使用。显然,如果不受信任的代码可以使用该方法,你有一个安全问题。
前两个问题是重要的,但相当简单,将不会在这里进一步讨论。本节的其余部分告诉你如何控制脚本的访问数据和功能。
没有安全的最安全的(真的)
你是否担心你的蛇吃你的鼠标吗?没有吗?如果你没有蛇和鼠标,你不会有这个问题。
同样,如果你的应用程序不会有不可信用户(蛇)和敏感数据或危险的功能暴露给JavaScript(小鼠),那么你并不需要阅读任何进一步。函数和对象由JS_InitStandardClasses是安全的。他们不提供存取文件,网络,或其他任何浏览器相关的。最敏感的信息暴露给脚本是当前的日期和时间。
基于对象的能力的安全性
保持蛇吃鼠标的方法之一是在单独的笼子里,以保持鼠标和蛇。
一种方式来保持用户A访问用户B的敏感数据或危险的功能是保持每个用户的代码在一个独立的沙箱。也就是说,为每个用户创建一个单独的的JSContext和全局对象,并总是在适当的上下文中运行每个脚本。设立一个新的全局对象时,根本就没有定义任何功能,用户不应该访问。这种方法被称为对象功能的安全性。要更多地了解它,看电影或读了这本书。
隐喻是具误导性的一个方面:蛇鼠标因为有一个物理屏障的方式不能达到。随着SpiderMonkey的形势更加微妙。有没有障碍,只是有没有办法从这里到达那里。恶意脚本如何才能得到一个参考对象从另一个沙箱?它可能是在一个平行的宇宙。即使是全局变量是每个沙箱。有没有getObjectFromOtherSandbox()函数。你的应用程序只需要照顾,不暴露任何此类功能的脚本。
总之,从来没有一个用户的数据和对象传递到另一个用户的代码,你会不具有访问控制的问题。 SpiderMonkey的将这样做,如果你不这样做。
权衡。对象的能力,安全性是没有运行时安全检查的安全性。这是很容易实现,容易的原因有关,和快速。但公平的,也有一些缺点。首先,故障模式是相当严重的。如果你不小心泄漏到另一个对象从一个沙箱,魔鬼的瓶子。一旦恶意脚本获得一个沙盒中的单个对象的引用,它可以使用该对象得到一个参考的沙箱的全局对象,并从那里,几乎任何其他对象或函数在该沙箱。有没有办法解决它除了都灭沙箱,并开始了。第二个缺点是,系统不会自动响应用户权限的变化。假设用户A是不是管理员,所以你设置了沙箱A与没有管理员功能。如果你推广的用户A是一个管理员,SpiderMonkey的不会奇迹般地更新沙箱到具有管理员以前您没有定义的类和函数。您的应用程序将不得不这样做,明确。相反,如果你想去掉用户A的管理员权限,但是你已经给管理员功能,用户A的脚本,这是雪上加霜。你别无选择,只能破坏用户A的沙箱,并重新开始。
细粒度的安全性
另一种方法是不断观看蛇吃蛇鼠标,如果它试图吃鼠标,干预。
SpiderMonkey的设计,支持自定义应用程序定义的安全模型。例如,Firefox浏览器有一个复杂和强大的安全模式。一些JavaScript代码(“铬”)拥有系统的完全访问。从网页的脚本(“内容”)有非常有限的访问。同源策略管理脚本的访问数据和从其他网页的功能。
的SpiderMonkey的安全模型是基于Java校长的安全模型。该模型提供了一个通用的安全接口,但实际的安全的实现是由你。
要使用的SpiderMonkey的细粒度的安全功能:
决定要强制执行安全政策。
插入调用JS_CheckAccess在你的应用程序的安全检查是必要的,在每一个点。 (有些安全检查也被内置到JavaScript引擎,你必须决定什么样的安全策略来执行这些检查。)
实现一个或多个JSPrincipals在您的应用程序中的对象。您需要为每个组不同的权限脚本可能有一个的JSPrincipals对象。
当编译或执行代码,使用JSAPI重视校长的函数,编译后的代码。这些功能有的名义ForPrincipals。在下面列出。使用这些功能的目的是,以确保您的访问检查回调是谁试图访问一个对象的准确信息。
功能用途
JS_CompileScriptForPrincipals
JS_CompileUCScriptForPrincipals
JS_CompileFileHandleForPrincipals编译脚本与安全信息。 (执行编译的脚本,使用JS_ExecuteScript。)
JS_CompileFunctionForPrincipals
的JS_CompileUCFunctionForPrincipals创建一个JavaScript函数与安全信息。
JS_EvaluateScriptForPrincipals
的JS_EvaluateUCScriptForPrincipals编译并执行一个脚本与安全信息。
实现访问检查回调函数(见JSClass.checkAccess JS_SetCheckObjectAccessCallback)。这些将被称为JS_CheckAccess有时在JavaScript引擎。访问检查回调函数可以使用jsdbgapi.h的功能如JS_FrameIterator JS_StackFramePrincipals取得正试图执行检查操作的代码校长。然后,它确定是否允许该操作继续进行。
跟踪和分析
有JSAPI提供的功能,使其更容易实现的JavaScript示踪剂和分析器。
追踪功能
你配置 - 使跟踪jscalls的如果,您可以使用JS_SetFunctionCallback()设立一个JavaScript函数时被称为是被称为一个C函数,或已执行完毕:
1 void funcTransition(const JSFunction *func, 2 const JSScript *scr, 3 const JSContext *const_cx, 4 JSBool entering) 5 { 6 JSContext *cx = const_cast<JSContext*>(const_cx); 7 JSString *name = JS_GetFunctionId((JSFunction*)func); 8 const char *entExit; 9 const char *nameStr; 10 11 /* build a C string for the function's name */ 12 13 if (!name) { 14 nameStr = "Unnamed function"; 15 } else { 16 nameStr = JS_EncodeString(cx, name); 17 } 18 19 /* build a string for whether we're entering or exiting */ 20 21 if (entering) { 22 entExit = "Entering"; 23 } else { 24 entExit = "Exiting"; 25 } 26 27 /* output information about the trace */ 28 29 printf("%s JavaScript function: %s at time: %ld", entExit, nameStr, clock()); 30 } 31 32 void enableTracing(JSContext *cx) { 33 JS_SetFunctionCallback(cx, funcTransition); 34 } 35 36 void disableTracing(JSContext *cx) { 37 JS_SetFunctionCallback(cx, NULL); 38 }
英文原文: