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調用的應用程序中將會用到這些功能分類中的某些部分,象運行時控制和數據類型操作。例如,在你調用其他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);
初始化內置的和全局的JS對象
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);
處理統一碼(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數據類型
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引擎也使用JS值,稱之爲jsval
。一個jsval
本質上是一個指向任意JS數據類型(除了整型)的一個指針。對於整型,jsval
直接包含了他自身的整數值。在其他的情況下,指針還會被編碼,添加關於它所指的數據的類型的額外信息。使用可以提高引擎的效率,同時也可以讓很多API函數來處理不同類型的數據。
引擎API包含了一組用來測試JS值中的JS數據類型的宏。有:
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
。
操作JS字符串
在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函數。 |
|
編譯和執行一個啓用安全控制的腳本。 |
|
編譯並執行一個啓用安全控制且用統一碼編碼的腳本。 |
實現教程:
SpiderMonkey
SpiderMonkey的世界
運行時環境
上下文
全局對象
在C++中執行JavaScript腳本
第1步 - 創建JavaScript運行時環境(runtime)
JSRuntime *rt = JS_NewRuntime(1000000L); if ( rt == NULL ) { // Do some error reporting }
第2步 - 創建一個上下文(context)
JSContext *cx = JS_NewContext(m_rt, 8192); if ( cx == NULL ) { // Do some error reporting }
第3步 - 初始化全局對象(global object)
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步 - 執行腳本
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);
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對象
// 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; };
// 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 };
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對象
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; }
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; }
void JSCustomer::JSDestructor(JSContext *cx, JSObject *obj) { JSCustomer *p = JS_GetPrivate(cx, obj); delete p; p = NULL; }
第3步 - 增加屬性
static JSPropertySpec customer_properties[]; enum{name_prop,age_prop};
JSPropertySpec JSCustomer::customer_properties[] = { { "name", name_prop, JSPROP_ENUMERATE }, { "age", age_prop, JSPROP_ENUMERATE }, { 0 } };
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; }
第4步 - 增加方法
static JSFunctionSpec customer_methods[];
JSFunctionSpec wxJSFrame::wxFrame_methods[] = { { "computeReduction", computeReduction, 1, 0, 0 }, { 0 } };
static JSBool computeReduction(JSContext *cx, JSObject *obj, uintN argc,
jsval *argv, jsval *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();
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();