您好,登录后才能下订单哦!
怎么进行JavaScript-C/C++引擎嵌入开发,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。
本文档提供了一个JavaScript(JS)引擎的C语言实现的概述,他介绍了你如何在你的应用程序中嵌入脚本引擎来让它们可以使用JS。有两大理由让你在应用程序中嵌入JS引擎:使用脚本来自动操作你的应用程序;同时使用JS引擎和脚本无论何时都可以提供跨平台的功能并消除了应用程序解决方案对平台的依赖性。
本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调用的应用程序中将会用到这些功能分类中的某些部分,象运行时控制和数据类型操作。例如,在你调用其他JS功能之前,你必须通过调用JS_NewRuntime
函数来新建和初始化JS引擎。其他功能分类,像安全控制,提供一些可选的特性,你可以根据需要在你的应用程序中使用它们。
从概念上来讲,JS引擎在你的系统上是一个共享资源。通过在你的应用程序中嵌入引擎API命令你可以向JS引擎传递处理的请求。这个引擎,反过来,处理你的请求,并把返回值或者状态信息返回给你的应用程序。图1.1描述了它们一般的关系:
图 1.1
例如,假设你正在使用JS引擎来使你的应用程序能通过JS脚本自动运行,同时假设你的应用程序运行一个脚本来对一个用户进行身份验证并且设置一个用户对这个应用程序的访问权限。首先,你的应用程序可能新建一个代表用户的自定义JS对象,包括了用户的名字、ID、访问权限和一个可能的用户拥有权限在应用程序中使用的函数的列表。
在这个情况下,你的应用程序给JS引擎发送的的第一个请求可能是对JS_NewObject
的调用来新建一个自定义对象。当JS引擎新建了这个对象,他返回一个指针给你的应用程序。你的应用程序可以再次调用JS引擎来执行使用这个对象的脚本。例如,在建立了用户对象之后,你的应用程序会立刻给JS_EvaluateScript
传递一个脚本来立刻编译执行。那个脚本可以获得并验证用户信息,然后建立用户对其他应用程序特性的访问权限。
事实上,你的应用程序和JS引擎之间的关系远比图1.1中显示的要复杂的多。例如,它假设你已经为你的平台构建了JS引擎。它还假设你的应用程序包含了jsapi.h
还假设应用程序对引擎进行的第一个调用已经初始化了JS运行时。
当JS引擎接受到了一个初始化的请求时,他会为JS运行时分配内存。图1.2描述了这个过程:
图 1.2
这个运行时是一个内存空间,在其中可以维护你的应用程序所使用的变量、对象和上下文。一个上下文是指,针对JS引擎所使用的线程的脚本执行状态。每个同时存在的脚本或者线程都必须有它自己的上下文。一个单独的JS运行时可以包含很多上下文、对象和变量。
几乎所有的JS引擎调用都要求有一个上下文的参数,所以在创建了运行时之后你的应用程序首先要做的一件事情是调用JS_NewContext
来至少创建一个上下文。实际你需要的上下文数量由你的应用程序中所期望同时运行的脚本的数量决定。从另一方面说,如果同一时间只有一个脚本被编译执行,那么你就知需要建立单独的一个上下文,你可以对每个脚本重复使用它。
在你新建了上下文之后,你会通常想要初始化引擎内置的JS对象,可以通过调用JS_InitStandardClasses
实现。内置的对象有Array
,Boolean
,Date
,Math
,Number
,和String
字符串对象,大多数脚本都会用到。
大多数应用程序也要用到自定义JS对象。这些对象是特定于你的应用程序的。他们通常代表了数据结构和应用程序中脚本使用的方法。要新建一个自定义对象,你要组装一个JS类来生成这个对象,调用JS_InitClass
来在运行时设立这个类,然后调用JS_NewObject
来在引擎中新建你这个自定义对象的实例。最后,如果你的对象有一些属性,你也许要通过调用JS_SetProperty
来设置他们的默认值。
即使你在创建一个对象的时候给JS引擎传递了一个特定的上下文,最后这个对象还是独立于这个上下文存在的。任何脚本都可以和任意上下文相关联来访问任何对象。图1.3描述了脚本和运行时、上下文以及对象之间的关系。
图 1.3
如图1.3所示,脚本和上下文完全是互相独立存在的及时他们可以访问相同的对象。在给定的运行时中,一个应用程序可以任意未分配的上下文来访问任何对象。也可能有时你想确保能为独占的使用而保留某些上下文和对象。在这些情况下,给你的应用程序新建单独的运行时:一个针对共享上下文和对象,另一个(或者更多的,取决于你的应用程序的需求)针对私有的运行时和对象。
注意:同一时间只能有一个线程被授权访问特定上下文。
在你可以在你的应用程序中使用JS之前,你必须将JS引擎构建成一个可共享的库。在大多数情况下,引擎代码已经包括了Make文件来自动构建。
例如,在Unix下,js
源代码目录包括了一个基本的Gnu Make文件——Makefile.ref
和一个config
目录。config
目录包括了平台特定的.mk
文件来配合Makefile.ref
对你的环境进行构建。在Windows
NT下,NMake文件是js.mak
。
请阅读源代码目录中任何的readme
文件,也许其中包括了和更新的编译指导或者其他信息。
如果要让你的应用程序可以执行JS,就要在你的应用程序代码中嵌入合适的引擎。嵌入一般有五步:
在你的C模块中加入#include jsapi.h
来确保编译器知道有哪些引擎的API可以调用。极少部分特殊的JS引擎工作时会要求你包含额外的头文件。例如,要在你的应用程序中调用JS调试器,你要在合适的模块里面包含jsdbgapi.h
。
大部分在JS源代码中的其它的头文件不应该被引用。这样做可能会使你的程序依赖于引擎内部的接口,而这些接口可能随着版本发布而更改。
在你的应用程序中提供支持结构和变量声明。例如,如果你打算给JS引擎传递一个脚本呢,提供一个字符串变量保存了你的应用程序的脚本的版本的文字信息。使用jsapi.h
中定义的JS数据类型来声明结构和变量。
使用JavaScript编写特定应用的对象。这些对象常常会与操作在你C程序中的结构的结构和方法进行通讯,特别是如果你在使用JS引擎来自动操作你的应用程序。
在程序代码中嵌入合适的JS引擎API调用和变量引用,包括初始化内置JS对象,和创建组成任何应用程序要用的自定义对象。
大多数JS引擎调用都会返回一个值。如果这个值是零或者空,它通常表示一个错误的情况发生了。如果值非零,它一般表示成功;在这些情况下,返回的值常常会是你的程序需要使用的指针,或者存起来留以后引用。很重要的是,你的程序至少应该每次检查JS调用返回的值。
以下代码片断描述了嵌入使用的大部分过程,除了JS脚本的建立,这点也不在本文的介绍范围之内。如要查询有关创建脚本的信息——JavaScript这个语言——请看客户端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引擎调用所必须的关键元素。如果想要更完整的例子——也就是以上这段代码片断的出处——参见js.c
,这个范例应用的源代码是包含在JS引擎的源代码中的。
大多数你要创建的JavaScript应用,你都会想要遵循一些权威的的JS API嵌入实践。以下部分讲述了任何程序中都要使用到的API调用。
在很多情况下,嵌入某些特定API调用的顺序决定这个程序的成功。例如,你必须在调用其它JS引擎函数之前初始化一个JS运行时。相应的,你要在关闭程序之前释放JS运行时。因此,典型的程序的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运行时是一块内存空间,在这里面JS引擎可以管理与JS函数和脚本相关的上下文、对象和变量。在执行任何JS函数或者是脚本之前,你必须初始化一个运行时。初始化运行时的API调用是JS_NewRuntime
。JS_NewRuntime
有一个参数,是一个无符号整数,它指明了在垃圾收集发生之前,分配给运行时的内存最大数值,单位是字节。例如:
rt = JS_NewRuntime(8L * 1024L * 1024L);
如上面列举的,JS_NewRuntime
也会返回一个值,这个值是一个指向新建的运行时的指针。一个非空的返回值表示运行时被成功创建了。
一般来说,一个应用程序只需要一个运行时。但是,你还是可以创建多个运行时的,我们可以在必要的时候调用JS_NewRuntime
并把返回值存在不同的指针中。
当不再需要JS运行时的时候,应该把它销毁来释放他占用的内存资源,以便给其他应用程序来使用。根据你的应用程序中JS的使用范围,你可以选择在JS使用结束立刻销毁运行时,或者,你可以选择一直保留运行时知道你的应用程序即将结束。无论哪种情况,都必须使用JS_DestroyRuntime
来释放运行时,当运行时不再需要的时候:
JS_DestroyRuntime(rt);
如果你使用了多个运行时,要确保在结束应用程序前,每一个都被正确释放了。
几乎所有的JS API调用都要求你传送一个上下文作为参数。在JavaScript引擎中,一个上下文唯一对应一个脚本。引擎把上下文信息传送给运行脚本的那个线程。每个同步执行的脚本必须被分配一个唯一的上下文。当一个脚本执行完之后,他的上下文就不再被使用了,这时候这个上下文就可以再次被分配给一个新的脚本,或者可以释放他。
要为一个脚本创建新的上下文,可以使用JS_NewContext
函数。该函数有两个参数:一个指针指向上下文所需结合的运行时,和为上下文分配的栈空间的大小,以字节为单位。如果成功,函数返回新建的上下文的指针。例如:
JSContext *cx;
.
.
.
cx = JS_NewContext(rt, 8192);
运行时必须已经存在。你为上下文指定的栈的大小应该足够容纳使用这个上下文的脚本所创建的任何变量和对象。注意和分配和维护上下文相关有一个特定的数量,因为你可能要:
只创建同一时刻在你的应用程序中所需要的数量一样多的上下文。
只要上下文有可能被应用程序用到,就保留他们,而不是每当需要的时候再重新新建不需要了就立刻销毁。
当不再需要某一个上下文时,应该把它销毁来释放它占用的内存资源留给其他的应用使用。根据你的应用程序中的JS使用范围,你可以选择在使用完上下文之后,就立刻销毁,或者,更多情况下,你可以考虑为以后重复使用来保留上下文直到应用程序结束为止。不管哪种情况,当他不再需要用到的时候,可以使用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);
JavaScript引擎提供了一些内置对象,他们会简化你的某些开发任务。例如,内置的Array
对象让你更方便地在JS引擎中创建和处理数组结构。类似,Date
对象提供了一个统一的处理日期数据的机制。要查阅引擎支持的内置对象的完整列表,请参看JS_InitStandardClasses。
JS引擎始终使用函数和全局对象。一般来说,全局对象存在于JS脚本的场景背后,为所有其它JS对象提供了一个默认的空间范围和存储了你在程序中创建和使用的全局变量。在你创建你自己的对象之前,你需要初始化全局对象。函数对象将启用对象支持和构造器调用。
JS_InitStandardClasses
, 这个API调用将初始化全局和函数对象还有引擎内置的对象,这样你的应用程序就可以使用他们了:
JSBool builtins; . . . builtins = JS_InitStandardClasses(cx, glob);
JS_InitStandardClasses
会返回一个JS boolean值来表示初始化是否成功。
你可以为你的应用程序指定一个不同的全局对象。例如,Netscape Navigator就使用了他自己的全局对象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指导》。
在应用程序中嵌入合适的JS引擎调用来编译和执行脚本。你有两种选择:1.) 仅使用一个函数调用来编译和执行脚本:JS_EvaluateScript
,JS_EvaluateUCScript
或者2.)
使用JS_CompileScript
或者JS_CompileUCScript
,来一次性编译脚本,然后可以用一个独立的函数调用JS_ExecuteScript
.
来重复执行已经编译的代码。这些调用的“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
并且把对象作为一个参数传递。这个函数将返回一个指向对象私有数据的指针:
pdata =JS_GetPrivate(cx, obj);
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
*。
JavaScript定义了他自己的数据类型。其中一部分直接对应C中的副本。其他的,诸如JSObject
,jsdouble
,
和
JSString
,对
JavaScript有特殊意义。
一般而言,你在应用程序中声明和使用JS数据类型就和使用标准C数据类型一样。然而,JS引擎对JS数据类型,也就是需要超过一个字空间的变量变量JSObject
,jsdouble
,
和JSString
有不同的跟踪。引擎周期性地检查这些变量,察看他们是否还在使用中。如果不再使用了,就收集他们,释放存储空间来重新使用。
垃圾收集可以有效重复利用堆的资源,但是过分频繁的垃圾收集也会对性能造成影响。你可以根据JS运行时控制垃圾收集的频率,根据你给程序分配的JS运行时的大小和你应用程序使用的JS变量和对象的数量之间的关系。如果你的程序要创建和使用很多JS对象和变量,你可能就要分配足够大的运行时来减少垃圾收集的可能频率。
注意你的应用程序要在任何时候调用同样能
JS_GC
或者JS_MaybeGC
来强制进行垃圾收集。JS_GC
将强制进行垃圾收集。JS_MaybeGC
则会根据条件进行垃圾收集,如果你调用这个函数时,初始化时分配的空间的特定比例已经被使用的话,就进行垃圾收集。
除了JS数据类型之外,JS引擎也使用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
Besides testing ajsval
,你也可以检测他是否属于一个基本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
。
在JavaScript中你的很多工作都回涉及字符串。JS引擎实现了一个JS字符串类型,JSString
,一个指向JS字符—jschar
—数组的指针,用来处理支持统一码的字符串。引擎也实现了一系列丰富的通用和统一码字符串管理程序。最后,JS引擎提供了对限定字符串的支持,这可以将两个或多个相同的字符串创建时在内存中共享一个单独的实例。对于JSString
类型的字符串,引擎会跟踪和管理字符串资源。
通常情况下,当你在处理JS引擎使用的字符串时,你应该使用JS API中的字符串处理函数来创建和复制字符串。还有创建以空字符结尾的和特定长度的字符串的例程,以及获取字符串长度和比较字符串。
使用统一码(Unicode)的API字符串函数的名称和标准的引擎API字符串行数是一一对应的,规则如下:如果标准函数名是JS_NewStringCopyN
,相应的统一码版本就是JS_NewUCStringCopyN
。同样有针对限定字符串的支持统一码的API字符串函数。
为了节省存储空间,JS引擎提供了对共享一个单独的字符串实例支持,这些字符串属于一些独立的不可变化的文字。这种被共享的字符串被称为“限定字符串”(interned string)。当你觉得某个特定的文本会被创建并且反复在程序中使用多次的话,那可以使用限定字符串。
引擎的API提供了几种工作于限定字符串的函数调用:
JS_InternString
,
用来创建或者复用一个JSString
.
JS_InternUCString
,
用来创建或者复用一个统一码的JSString
.
JS_InternUCStringN
,
用以创建或者复用一个固定长度的统一码JSString
。
现在使用JavaScript 1.3,JS引擎加入了安全性增强API函数来编译和执行传送给引擎的脚本和函数。JS安全模型是基于Java的基本安全模型的。该模型提供了一个公共安全接口,但是实际的安全控制由你去实现。
在使用JavaScript的应用中使用安全管理的一个常用的方法是比较脚本的来源和限制脚本的交互。例如,你可能会比较两个或多个脚本的代码源并且只允许来自相同的代码源的脚本修改共享代码源的脚本的属性。
如要实现安全JS,请按照以下几步:
在代码中声明一个或多个JSPrincipals
类型的结构体(struct)。
把实现了安全信息的函数列表添加到数组中。这些包括了为程序提供原则数组的函数,和使用给定原则的JS对象的引用计数增减机制。
把JSPrincipals
结构和你的安全信息组装起来。这个信息可以包括一般代码源信息。
在运行时,使用一些特定的JS API调用来编译和执行所有要应用安全性的脚本和函数,他们将要求传递一个JSPrincipals
结构。下面的表格列出了这些API函数和他们的作用:
函数 | 目的 |
| 编译(但是不执行)一个启用安全控制的脚本。 |
| 编译(但不执行)一个启用安全控制、统一码编码的脚本。 |
| 从一个文本串创建一个启用安全控制的JS函数。 |
| 从一个统一码编码的字符串中创建一个带安全信息的JS函数。 |
| 编译和执行一个启用安全控制的脚本。 |
| 编译并执行一个启用安全控制且用统一码编码的脚本。 |
文章来源:url https://developer.mozilla.org/en-US/docs/SpiderMonkey/JSAPI_User_Guide
实现教程:
SpiderMonkey是Mozilla项目的一部分,是一个用C语言实现的JavaScript脚本引擎,另外还有一个叫做Rhino的Java版本。
首先你要下载SpiderMonkey的最新版本。下载包是以源代码形式分发的。这就需要你自己编译脚本引擎。对于Visual C++用户来说可以在src-目录找到工程文件。编译结果是一个叫作"js32.dll"的动态库。SpiderMonkey还能应用于
Macintosh及Unix平台之上。想了解如何在其他平台编译SpiderMonkey请参阅ReadMe.html。
为了在SpiderMonkey中运行JavaScript代码,应用程序必须有三个要素:JSRuntime,JSContext和全局对象。
JSRuntime,为其中的JavaScript变量、对象、脚本和应用程序中使用的上下文分配空间。每个JSContext和脚本中的每个对象都生活在一个 JSRuntime中。他们不能转移到其他运行时上或在与其它运行时共享。一般来说大多数应用程序只需要一个运行时环境。
JSContext,就像是一台小机器,它涉及JavaScript代码和对象的很多东西。它可以编译和执行脚本、获取和设置对象属性、调用JavaScript函数、一种类型转换为另一种JavaScript数据、创建对象,等等。几乎所有JSAPI函数都要一个JSContext*作为其第一个参数,就像<stdio.h>中的大多数函数都需要FILE*一样.
全局对象包含所有可以在JavaScript代码中使用的类、函数和变量。
当JavaScript代码要做一些事时,比如window.open("http://www.mozilla.org/"),实际上它是在访问一个全局属性(全局对象的属性),在这里是window。
脚本能看到的全局属性完全由应用程序控制。应用程序首先创建一个对象并加入JavaScript标准类,如Array和Object。然后加入任何程序想加入的自定义的类、函数和变量(象这里的window)。应用程序每次运行js脚本(例如使用JS_EvaluateScript)时提供了该脚本使用的全局对象。至于脚本,它也可以创建自己全局函数和变量。所有的这些函数、类和变量都作为属性存储在全局对象中。
JavaScript运行时环境通过调用JS_NewRuntime来初始化。它负责分配运行时环境所需的内存资源。你要指定所分配的字节数,超过这个大小时碎片收集器将会执行。
JSRuntime *rt = JS_NewRuntime(1000000L);if ( rt == NULL ) {// Do some error reporting}
上下文指定脚本中栈的大小,并分配指定大小的内存作为脚本的执行栈。每个脚本关联于自己所拥有的上下文。当上下文被一个脚本或线程使用时,其他脚本或线程不能使用。当脚本或线程结束,这个上下文就可以被重用于下一个脚本或线程。
用JS_NewContext方法创建新的上下文。一个上下文和一个运行时环境关联,你必须指定栈的大小。
JSContext *cx = JS_NewContext(m_rt, 8192); if ( cx == NULL ) { // Do some error reporting}
在脚本执行前,你必须初始化JavaScript普通函数并创建脚本中的大量对象。
全局对象(global object)用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);
执行脚本的一种途径是用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);
在应用程序结束前, 必须清理脚本引擎.
JS_DestroyContext(cx); JS_DestroyRuntime(rt);
在例子中使用的类如下:
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; };
创建一个允许被JavaScript脚本使用的C++类。
通过JSClass结构来定义JavaScript对象。我们要把它作为公共静态(public static)成员,因为这个结构要被其他类使用。另外它还可以被用于其他类来确定对象类型。(参考 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 对象. 你可以用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);
创建另一个叫 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; }
静态方法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; }
这个构造器方法可以有多个参数, 能用于初始化你的类. 现在你已经在堆上创建了一个指针, 你也需要一种途径销毁这个指针. 这可以通过静态方法 JS_Destructor 来完成.
void JSCustomer::JSDestructor(JSContext *cx, JSObject *obj) { JSCustomer *p = JS_GetPrivate(cx, obj); delete p; p = NULL; }
增加一个类型为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 将表示在你的对象中没有发现这个属性.
创建类型为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 JSCustomerJSCustomer::JSInit(cx, globalObj);// Execute the scriptjsval 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_Hclass 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();
看完上述内容,你们掌握怎么进行JavaScript-C/C++引擎嵌入开发的方法了吗?如果还想学到更多技能或想了解更多相关内容,欢迎关注亿速云行业资讯频道,感谢各位的阅读!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。