SpiderMonkey-讓你的C++程序支持JavaScript腳本

譯序

有些網友對為什么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示例腳本程序。

?

轉載于:https://www.cnblogs.com/mfrbuaa/p/4251455.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/257704.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/257704.shtml
英文地址,請注明出處:http://en.pswp.cn/news/257704.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Android 虛擬機學習總結Dalvik虛擬機介紹

1、Dalvik虛擬機與Java虛擬機的最顯著差別是它們分別具有不同的類文件格式以及指令集。Dalvik虛擬機使用的是dex&#xff08;Dalvik Executable&#xff09;格式的類文件&#xff0c;而Java虛擬機使用的是class格式的類文件。一個dex文件能夠包括若干個類。而一個class文件僅僅…

des vue 加密解密_vue DES 加密

ECB模式import cryptoJs from crypto-js// DES加密export const encryptDes (message, key) > {var keyHex cryptoJs.enc.Utf8.parse(key)var option { mode: cryptoJs.mode.ECB, padding: cryptoJs.pad.Pkcs7 }var encrypted cryptoJs.DES.encrypt(message, keyHex, op…

使用jQuery清空表單

$(#theform)[0].reset(); reset()這個函數的作用是將表單的值重置&#xff0c;變為默認值&#xff0c; 例&#xff1a; <input type"text" value"姓名"> 這個標簽的默認值就是”姓名“&#xff0c;如果使用上面的方法&#xff0c;就會重置為“姓名”…

MobX快速入門教程(重要概念講解)

轉載請注明原文地址&#xff1a;http://www.cnblogs.com/ygj0930/p/7372119.html 一&#xff1a;Mobx工作流程圖 二&#xff1a;MobX涉及到的概念 1:狀態state 組件中的數據。 2:被觀察observable 被observable修飾的state數據將會暴露給整個app&#xff0c;各觀察者組件都可以…

CentOS工作內容(七)禁用IPV6

CentOS工作內容&#xff08;七&#xff09;禁用IPV6 用到的快捷鍵 tab 自動補齊(有不知道的嗎) ctrla 移動到當前行的開頭(a ahead) ctrle 移動到當前行的開頭(e end) ctrlu 刪除(剪切)此處至開始所有內容 復制進來&#xff1a;按一下鼠標右鍵粘貼到SercureCRT 復制出去&#x…

循環結束后變回去 設置一個值_VBA掌握循環結構,包你效率提高500倍

這是系列免費教程《Excel VBA&#xff1a;辦公自動化》&#xff0c;還是老規矩&#xff0c;看看我們走到哪里了。1.認識VBA&#xff1a;什么是VBA&#xff1f;2.這些掌握了&#xff0c;你才敢說自己懂VBA3.VBA變量5年踩坑吐血精華總結4.VBA中重要的強制申明&#xff0c;誰看誰明…

連接到kali linux服務器上的MySQL服務器錯誤

前言&#xff1a;想把數據庫什么的都放在虛擬機kali Linux里&#xff0c;但無奈出了好多錯誤。 首先&#xff1a;可以參照上一篇文章開啟kali服務器端的遠程連接功能&#xff0c;上一篇文章 然后&#xff1a;使用window端的sqlyog&#xff08;MySQL圖形化連接工具&#xff09;連…

dedecms后臺怎么添加發布軟件?織夢后臺軟件內容管理

使用織夢cms有很多的功能&#xff0c;其中有一個是在dedecms后臺添加發布軟件&#xff0c;然后在前臺大家可以直接下載軟件&#xff0c;在織夢cms后臺怎么添加發布軟件呢&#xff1f;下面是織夢軟件內容管理的主要操作步驟。使用織夢cms有很多的功能&#xff0c;其中有一個是在…

301 302區別_如何正確理解301,302和canonial標簽

今天我們來學習一下幾個比較容易混淆的頁面跳轉標簽&#xff0c;301&#xff0c;302&#xff0c;relcanonial。在谷歌SEO里面&#xff0c;我們比較容易常見的是第一個301&#xff0c;302和canonial出現的比較少&#xff0c;但是不代表不存在&#xff0c;我會嘗試從以下價格方面…

ffmpeg文檔08-表達式計算/求值

8 表達式計算/求值 在計算表達式時&#xff0c;ffmpeg通過libavutil/eval.h接口調用內部計算器進行計算。 表達式可以包含一元運算符、運算符、常數和函數 兩個表達式expr1和expr2可以組合起來成為"expr1;expr2" &#xff0c;兩個表達式都會被計算&#xff0c;但是新…

為什么手機游戲手柄沒有流行起來?

問答社區知乎上有人提了一個問題&#xff0c;“為什么手機用游戲手柄沒有流行&#xff1f;” Ta找了不少論證&#xff1a;1&#xff09;手機用戶數量很大&#xff1b;2&#xff09;大量用戶在手機上花費最多時間的是玩游戲&#xff1b;3&#xff09;游戲機平臺&#xff08;的游…

c++排序算法ppt_C/C++學習教程:C語言排序算法—插入排序算法

前言&#xff1a;插入排序算法是所有排序方法中最簡單的一種算法&#xff0c;其主要的實現思想是將數據按照一定的順序一個一個的插入到有序的表中&#xff0c;最終得到的序列就是已經排序好的數據。直接插入排序是插入排序算法中的一種&#xff0c;采用的方法是&#xff1a;在…

python函數參數

1.位置參數 2.默認參數 指向參數為不可變對象 3.可變參數 **args 一個列表list或是元組tuple 4.關鍵字參數 **kw,是一個字典dict 5.命名關鍵字參數 *, 轉載于:https://www.cnblogs.com/aliy-pan/p/5198025.html

Python 常用函數 configparser模塊

使用ConfigParser模塊讀寫ini文件 ConfigParserPython的ConfigParser Module中定義了3個類對INI文件進行操作。分別是RawConfigParser、ConfigParser、SafeConfigParser。模塊所解析的ini配置文件是由多個section構成&#xff0c;每個section名用中括號‘[]’包含&#xff0c;每…

自制Unity小游戲TankHero-2D(3)開始玩起來

自制Unity小游戲TankHero-2D(3)開始玩起來 我在做這樣一個坦克游戲&#xff0c;是仿照&#xff08;http://game.kid.qq.com/a/20140221/028931.htm&#xff09;這個游戲制作的。僅為學習Unity之用。圖片大部分是自己畫的&#xff0c;少數是從網上搜來的。您可以到我的github頁…

mysql按月分列統計_實現mysql按月統計的教程

mysql有個字段是DATETIME類型&#xff0c;要實現可以按月統計&#xff0c;該怎么寫sql語句&#xff1f;select month(f1) from tt group by month(f1)or select DATE_FORMAT(f1,%m) from tt group by DATE_FORMAT(f1,%m)比如數據庫的為2008-01-15 12&#xff1a;10&#xff1a;…

Log4j的擴展-支持設置最大日志數量的DailyRollingFileAppender

Log4j現在已經被大家熟知了&#xff0c;所有細節都可以在網上查到&#xff0c;Log4j支持Appender&#xff0c;其中DailyRollingFileAppender是被經常用到的Appender之一。在討論今天的主題之前&#xff0c;我們先看下另外一個Appender。 最常用的Appender——RollingFileAppend…

VirtualBox虛擬機安裝CentOS 7

新建虛擬機 因為比較簡單&#xff0c;所以對于VirtualBox就不做過多介紹了&#xff0c;直接下載安裝即可&#xff0c;安裝好之后打開Oracle VM VirtualBox管理器&#xff0c;點擊新建&#xff0c;選擇Red Hat&#xff08;根據windows主機選擇 32/64 bit&#xff0c;通常會自動識…

mysql 指定賬戶已存在_安裝mysql時告訴我指定的賬戶已存在?

{"moduleinfo":{"card_count":[{"count_phone":1,"count":1}],"search_count":[{"count_phone":4,"count":4}]},"card":[{"des":"阿里云數據庫專家保駕護航&#xff0c;為用戶…

C語言:用字符讀取流和輸出流來讀寫入數據。(文本文件)

/* 文件的幾種操作模式: r:只讀 w:只寫 rw:可讀可寫 文件的分類&#xff1a; t:文本文件(字符文件) b:二進制文件(字節文件)注意&#xff1a; 采用只讀方式打開文件時,如果源文件不存在,打開文件會失敗&#xff01; 采用只寫方式打開文件時,不管源文件存不存在,都不會失敗…