譯序
有些網友對為什么D2JSP能執行JavaScript腳本程序感到奇怪,因此我翻譯了這篇文章,原文在這里。這篇教程手把手教你怎樣利用SpiderMonkey創建一個能執行JavaScript腳本的C++程序,并讓JavaScript腳本操縱你的C++程序的內部數據、操作。從這篇教程能夠看到在SpiderMonkey引擎的幫助下,讓C++程序支持JavaScript腳本是一件非常easy的事,更棒的是SpiderMonkey也能夠在Macintosh和Unix平臺使用。
SpiderMonkey是Gecko(Firefox瀏覽器的內核)的JavaScript腳本引擎,具體文檔請看這里。
下面為翻譯內容。
------------------------------------------------
本教程的目的是教你怎樣用JavaScript做為腳本語言使你的C++程序自己主動化。
SpiderMonkey
SpiderMonkey是Mozilla項目的一部分,用C語言寫成,是負責運行JavaScript腳本的引擎。另外另一個叫Rhino的Java引擎。
SpiderMonkey的最新版本號可在這里下載。它是以源碼形式公布的,因此你必須自己編譯它(譯注:事實上網上有非常多編譯好的二進制版本號,google一下js32.dll就可找到)。Visual C++用戶能夠在src文件夾下找到Workspace項目project文件來編譯,編譯結果會產生一個叫'js32.dll'的dll文件。
SpiderMonkey也能夠在Macintosh和Unix上使用,想了解怎樣在這些平臺上進行編譯請閱讀Readme.html。
在C++中運行JavaScript程序
步驟1-創建JavaScript runtime(執行時實例)
初始化一個JavaScript runtime可用JS_NewRuntime方法,該方法將為runtime分配內存,同一時候還得指定一個字節數,當內存分配超過這個數字時垃圾收集器會自己主動執行。
JSRuntime?*rt?=?JS_NewRuntime(1000000L);
if?(?rt?==?NULL?)

...{
????//?Do?some?error?reporting
}

步驟2-創建context(上下文環境)
Context指明了腳本執行所需的棧大小,即分配給腳本執行棧的私有內存數量。每一個腳本都和它自己的context相關聯。
當一個context正在被某個腳本或線程使用時,其它腳本或線程不能使用該context。只是在腳本或線程結束時,該context能夠被下一個腳本或線程重用。
創建一個新context可用JS_NewContext方法。context必須關聯到一個runtime,調用JS_NewContext方法時還必須指定棧的大小。
JSContext?*cx?=?JS_NewContext(m_rt,?8192);
if?(?cx?==?NULL?)

...{
????//?Do?some?error?reporting
} 步驟3-初始化全局對象
在一個腳本開始執行前,必須初始化一些大多數腳本會用到的通用的JavaScript函數和內置(build-in)類對象。
全局對象是在一個JSClass結構中描寫敘述的。該結構能夠按下面方式初始化:
JSClass?globalClass?=

...{
????"Global",?0,
????JS_PropertyStub,??JS_PropertyStub,
????JS_PropertyStub,?JS_PropertyStub,
????JS_EnumerateStub,?JS_ResolveStub,
????JS_ConvertStub,??JS_FinalizeStub
}; 如今創建和初始化這個全局對象:
JSObject?*globalObj?=?JS_NewObject(cx,?&globalClass,?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_EvaluteScript返回JS_TRUE代表運行成功,返回JS_FALSE則代表有發生錯誤。
從rval得到對應的字符串值能夠用以下的方法。在這里我不想解釋全部細節,想獲得更具體的信息請自己查API文檔。
JSString?*str?=?JS_ValueToString(cx,?rval);
std::cout?<<?JS_GetStringBytes(str); 步驟5-清理腳本引擎
程序結束前必須對腳本引擎做一些清理工作:
JS_DestroyContext(cx);
JS_DestroyRuntime(rt); 在C++中定義一個在JavaScript中用的類
這個樣例中用到的類定義例如以下:
class?Customer

...{
public:

????int?GetAge()?...{?return?m_age;?}

????void?SetAge(int?newAge)?...{?m_age?=?newAge;?}

????std::string?GetName()?...{?return?m_name;?}

????void?SetName(std::string?newName)?...{?m_name?=?newName;?}

private:
????int?m_age;
????std::string?m_name;
}; 步驟1-JavaScript類
從Customer類派生一個你想在JavaScript中用的新的C++類,或者創建一個包括一個Customer類型成員變量的新類。
給JavaScript用的類得有一個JSClass結構,為此得創建一個JSClass類型的靜態成員變量,該變量會被其它類用到,因此還得把它聲明為public變量。別的類能夠用該結構來推斷對象的類型(見JS_InstanceOf API)。
//?JSCustomer.h
class?JSCustomer

...{
public:
????JSCustomer()?:?m_pCustomer(NULL)?

????...{
????}

????~JSCustomer()

????...{
????????delete?m_pCustomer;
????????m_pCustomer?=?NULL;
????}

????static?JSClass?customerClass;

protected:
????void?setCustomer(Customer?*customer)?

????...{
????????m_pCustomer?=?customer;?
????}

????Customer*?getCustomer()

????...{
????????return?m_pCustomer;?
????}

private:
????Customer?*m_pCustomer;

}; 該JSClass結構里包括了JavaScript類的名字、標志位以及給腳本引擎用的回調函數的名字。舉個樣例,腳本引擎使用回調函數從類中獲取某個屬性值。
在C++類的實現文件里定義JSClass結構例如以下:
//?JSCustomer.cpp
JSClass?JSCustomer::customerClass?=?

...{
????"Customer",?JSCLASS_HAS_PRIVATE,
????????JS_PropertyStub,?JS_PropertyStub,
????????JSCustomer::JSGetProperty,?JSCustomer::JSSetProperty,
????????JS_EnumerateStub,?JS_ResolveStub,?
????????JS_ConvertStub,?JSCustomer::JSDestructor
}; 用到的回調函數是JSCustomer::JSGetProperty,JSCustomer::JSSetProperty和JSCustomer::JSDestructor。腳本引擎調用JSGetProperty獲取屬性值,調用JSSetProperty設置屬性值,調用JSDestructor析構JavaScript對象。
JSCLASS_HAS_PRIVATE標志位會讓腳本引擎分配一些內存,這樣你能夠在JavaScript對象中附加一些自己定義數據,比方能夠用它來保存類指針。
回調函數以C++的類靜態成員函數方式存在:
static?JSBool?JSGetProperty(JSContext?*cx,?JSObject?*obj,?jsval?id,?jsval?*vp);
static?JSBool?JSSetProperty(JSContext?*cx,?JSObject?*obj,?jsval?id,?jsval?*vp);
static?JSBool?JSConstructor(JSContext?*cx,?JSObject?*obj,?uintN?argc,?
????????????????????????????jsval?*argv,?jsval?*rval);
static?void?JSDestructor(JSContext?*cx,?JSObject?*obj); 步驟2-初始化你的JavaScript對象
創建另外一個叫JSInit的靜態方法,見以下的樣例,該方法將在應用程序創建JavaScript runtime時被調用。
static?JSObject?*JSInit(JSContext?*cx,?JSObject?*obj,?JSObject?*proto); JSInit方法的實現大約例如以下:
JSObject?*JSCustomer::JSInit(JSContext?*cx,?JSObject?*obj,?JSObject?*proto)

...{
????JSObject?*newObj?=?JS_InitClass(cx,?obj,?proto,?&customerClass,
????????JSCustomer::JSConstructor,?0,
????????JSCustomer::customer_properties,?JSCustomer::customer_methods,
????????NULL,?NULL);
????return?newObj;
} 對象在腳本中被具象化(譯注:instantiated,簡而言之就是對象new出來的時候)的時候,靜態方法JSConstructor會被調用。在這種方法中能夠用JS_SetPrivate API給該對象附加一些自己定義數據。
JSBool?JSCustomer::JSConstructor(JSContext?*cx,?JSObject?*obj,?uintN?argc,?
?????????????????????????????????jsval?*argv,?jsval?*rval)

...{
????JSCustomer?*p?=?new?JSCustomer();

????p->setCustomer(new?Customer());
????if?(?!?JS_SetPrivate(cx,?obj,?p)?)
????????return?JS_FALSE;
????*rval?=?OBJECT_TO_JSVAL(obj);
????return?JS_TRUE;
} JSConstructor構造方法能夠帶多個參數,用來初始化類。眼下為止已經在堆上創建了一個指針,還須要一種途徑來銷毀它,這能夠通過JS_Destructor完畢:
void?JSCustomer::JSDestructor(JSContext?*cx,?JSObject?*obj)

...{
????JSCustomer?*p?=?JS_GetPrivate(cx,?obj);
????delete?p;
????p?=?NULL;
} 步驟3-加入屬性
加入一個JSPropertySpec類型的靜態成員數組來存放屬性信息,同一時候定義屬性ID的枚舉變量。
static?JSPropertySpec?customer_properties[];
enum

...{
????name_prop,
????age_prop
}; 在實現文件里例如以下初始化該數組:
JSPropertySpec?JSCustomer::customer_properties[]?=?

...{?

????...{?"name",?name_prop,?JSPROP_ENUMERATE?},

????...{?"age",?age_prop,?JSPROP_ENUMERATE?},

????...{?0?}
}; 數組的最后一個元素必須為空,當中每一個元素是一個帶有3個元素的數組。第一個元素是給JavaScript用的名字。第二個元素是該屬性的唯一ID,將傳遞給回調函數。第三個元素是標志位,JSPROP_ENUMERATE代表腳本在枚舉Customer對象的全部屬性時能夠看到該屬性,也能夠指定JSPROP_READONLY來表明該屬性不同意被腳本程序改變。
如今能夠實現該屬性的getting和setting回調函數了:
JSBool?JSCustomer::JSGetProperty(JSContext?*cx,?JSObject?*obj,?jsval?id,?jsval?*vp)

...{
????if?(JSVAL_IS_INT(id))?

????...{
????????Customer?*priv?=?(Customer?*)?JS_GetPrivate(cx,?obj);
????????switch(JSVAL_TO_INT(id))

????????...{
????????case?name_prop:

????????????break;
????????case?age_prop:
????????????*vp?=?INT_TO_JSVAL(priv->getCustomer()->GetAge());
????????????break;
????????}
????}
????return?JS_TRUE;
}


JSBool?JSCustomer::JSSetProperty(JSContext?*cx,?JSObject?*obj,?jsval?id,?jsval?*vp)

...{
????if?(JSVAL_IS_INT(id))?

????...{
????????Customer?*priv?=?(Customer?*)?JS_GetPrivate(cx,?obj);
????????switch(JSVAL_TO_INT(id))

????????...{
????????case?name_prop:
????????????break;
????????case?age_prop:
????????????priv->getCustomer()->SetAge(JSVAL_TO_INT(*vp));
????????????break;
????????}
????}
????return?JS_TRUE;
} 建議在屬性的回調函數中返回JS_TRUE。假設返回JS_FALSE,則當該屬性在對象中沒找到時(腳本引擎)不會進行搜索。
步驟4-加入方法
創建一個JSFunctionSpec類型的靜態成員數組:
static?JSFunctionSpec?customer_methods[]; 在實現文件里例如以下初始化該數組:
JSFunctionSpec?wxJSFrame::wxFrame_methods[]?=?

...{

????...{?"computeReduction",?computeReduction,?1,?0,?0?},

????...{?0?}
}; 最后一個元素必須為空,當中每一個元素是一個帶有5個元素的數組。第一個元素是給腳本程序用的方法名稱。第二個是一個全局或者靜態成員函數的名稱。第三個是該方法的參數個數。最后兩個能夠忽略。
在類中創建一個靜態方法:
static?JSBool?computeReduction(JSContext?*cx,?JSObject?*obj,?uintN?argc,?
???????????????????????????????jsval?*argv,?jsval?*rval); 該函數成功時返回JS_TRUE,否則返回JS_FALSE。注意真正的JavaScript方法的返回值保存在rval參數中。
該方法的一個實現樣例:
JSBool?JSCustomer::computeReduction(JSContext?*cx,?JSObject?*obj,?uintN?argc,?
????????????????????????????????????jsval?*argv,?jsval?*rval)

...{
????JSCustomer?*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(); 別忘了在創建context時初始化JavaScript對象:
JSObject?*obj?=?JSCustomer::JSInit(cx,?global); 類常量
JavaScript類型
這一章解釋在JavaScript中會用到的幾種類型:Integer,String,Boolean,Double,Object和Function。
構建中。。。。。。
垃圾回收
構建中。。。。。。
下載
main.cpp演示怎樣運行一個javascript程序。JSCustomer.h演示Customer的JavaScript類的定義。JSCustomer.cpp演示JSCustomer的實現。Customer.h是Customer C++類的定義。example.js示例腳本程序。
?