http://www.ibm.com/developerworks/cn/linux/l-cn-spidermonkey/index.html

來源:互聯網
上載者:User
文章目錄
  • 基於 C 語言的 JavaScript 引擎探索
基於 C 語言的 JavaScript 引擎探索

使用 SpiderMonkey 指令碼化您的應用

邱 俊濤, 軟體工程師, 雲電同方研發中心

邱俊濤,畢業於昆明理工大學電腦科學與技術專業,對機械控制、電子、人工智慧、函數式編程等領域有濃厚的興趣,對電腦科學的底層比較熟悉。喜歡 C/JAVA/Python/JavaScript 等語言。現就職於雲電同方研發中心。

簡介: JavaScript 語言具有動態性,支援函數式編程,動態弱類型等等優點。作為一個指令碼語言,可以很方便的指令碼化需要高度可定製的應用程式。本文介紹基於 C 語言的 JavaScript 引擎 SpiderMonkey,詳細討論如何通過該引擎,使得 C 語言和 JavaScript 語言進行互動。

標記本文!

發布日期: 2011 年 2 月 17 日
層級: 初級
訪問情況 7621 次瀏覽
建議: 0 (添加評論)

平均分 (共 7 個評分 )

基礎知識

SpiderMonkey 簡介

和其他的 JavaScript 引擎一樣,SpiderMonkey 不直接提供像 DOM 這樣的對象,而是提供解析,執行 JavaSccript 代碼,記憶體回收等機制。SpidlerMonkey 是一個在 Mozilla 之下的開源項目,要使用 SpiderMonkey,需要下載其源碼,然後編譯為靜態 / 動態庫使用。

要在自己的應用程式中使用 SpiderMonkey,首先需要瞭解以下三個核心概念:

運行時環境運行時環境是所有 JavaScript 變數,對象,指令碼以及代碼的上下文所存在的空間。每一個內容物件,以及所有的對象均存在於此。一般應用僅需要一個運行時即可。

上下文上下文即指令碼執行的環境,在 SpiderMonkey 中,上下文可以編譯執行指令碼,可以存取對象的屬性,調用 JavaScript 的函數,轉換類型,建立 / 維護對象等。幾乎所有的 SpiderMonkey 函數都需要上下文作為其第一個參數 (JSContext *)。

上下文與線程密不可分,一般來講,單線程應用可以使用一個上下文來完成所有的操作,每一個上下文每次只能完成一個操作,所有在多線程應用中,同一時刻只能有一個線程來使用內容物件。一般而言,多線程應用中,每個線程對應一個上下文。

全域對象全域對象包含 JavaScript 代碼所用到的所有類,函數,變數。在 DOM 操作中,我們使用的:

 alter("something"); 

事實上使用的是全域變數 window 的一個屬性 alter( 這個屬性正好是一個函數 ),事實上上邊的語句在執行時會別解釋為:

 window.alter("something"); 

三者的關係如所示:

圖 1. 引擎內部結構依賴關係

安裝 SpiderMonkey

首先從 SpiderMonkey 的程式碼程式庫中下載其源碼包 js-1.7.0.tar.gz 本文在 Linux 環境下編譯,SpiderMonkey 的編譯安裝很容易:

 # 解壓縮 tar xvzf js-1.7.0.tar.gz  # 切換至源碼目錄 cd js-1.7.0/src  # 編譯 make -f Makefile.ref 

編譯完成之後,會產生一個新的目錄,這個目錄的名稱依賴於平台,比如在 Linux 下,名稱為:Linux_All_DBG.OBJ,其中包含靜態連結庫 libjs.a 和動態連結程式庫 libjs.so 等。本文後續的編譯環境就需要依賴於我們此處編譯出來的庫檔案。應該注意的是,此處編譯出來的庫檔案包含對調試的支援,體積較大,在應用程式發布時,可以去掉這些調試支援,使用下列重新編譯庫:

 # 建立非 debug 模式的庫 make BUILD_OPT=1 -f Makefile.ref 

Windows 及其他平台的編譯此處不再贅述,讀者可以自行參考 SpiderMonkey 的官方文檔。

JavaScript 對象與 C 對象間的轉換關係

JavaScript 是一門弱類型的語言,變數的值的類型在運行時才確定,而且可以在運行時被修改為其他類型的變數;而 C 語言,是一門靜態類型的語言,變數類型在編譯時間就已經確定。因此,這兩者之間變數的互訪就有了一定的難度,SpiderMonkey 提供了一個通用的資料類型 jsval 來完成兩者之間的互動。

事實上,在 C 代碼中定義的 jsval 類型的變數可以是 JavaScript 中的字串,數字,對象,布爾值,以及 null 或者 undefined。基於這個類型,SpiderMonkey 提供了大量的類型判斷及類型轉換的宏和函數。可以參看下錶:

表 1. JavaScript 對象與 C 對象轉換表

JavaScript 類型

jsval 類型判斷

jsval 常量

jsval 轉化

null

JSVAL_IS_NULL(v)

JSVAL_NULL

Undefined

JSVAL_IS_VOID(v)

JSVAL_VOID

Boolean

JSVAL_IS_BOOLEAN(v)

JSVAL_TRUE,

JSVAL_FALSE,

BOOLEAN_TO_JSVAL(b)

JSVAL_TO_BOOLEAN(v)

number

JSVAL_IS_NUMBER(v),

JSVAL_IS_INT(v),

JSVAL_IS_DOUBLE(v)

INT_TO_JSVAL(i),

DOUBLE_TO_JSVAL(d)

JSVAL_TO_INT(v),

JSVAL_TO_DOUBLE(v)

string

JSVAL_IS_STRING(v)

STRING_TO_JSVAL(s)

JSVAL_TO_STRING(v),

JS_GetStringChars(s),

JS_GetStringLength(s)

object

JSVAL_IS_OBJECT(v)

&& JSVAL_IS_NULL(v)

OBJECT_TO_JSVAL(o)

JSVAL_TO_OBJECT(v)

應該注意的是,jsval 有一定的缺陷:

  • jsval 並非完全的型別安全,在進行類型轉換之前,你需要明確被轉換的對象的真正類型,比如一個變數的值為 number 類型,而對其做向字串的轉化,則可能引起程式崩潰。解決方案是,在轉換之前,先做判斷。
  • jsval 是 SpiderMonkey 記憶體回收機制的主要目標,如果 jsval 引用一個 JavaScript 對象,但是垃圾收集器無法得知這一點,一旦該對象被釋放,jsval 就會引用到一個懸null 指標。這樣很容易使得程式崩潰。解決方案是,在引用了 JavaScript 對象之後,需要顯式的告知垃圾收集器,不引用時,再次通知垃圾收集器。

回頁首

簡單樣本

基本代碼模板

基本流程

使用 SpiderMonkey,一般來講會使用以下流程:

  • 建立運行時環境
  • 建立一個 / 多個內容物件
  • 初始化全域對象
  • 執行指令碼,處理結果
  • 釋放引擎資源

在下一小節詳細說明每個流程

代碼模板

使用 SpiderMonkey,有部分代碼是幾乎每個應用程式都會使用的,比如錯誤報表,初始化運行時環境,上下文,全域變數,執行個體化全域變數等操作。這裡是一個典型的模板:

清單 1. 必須包含的標頭檔

#include "jsapi.h"

引入 jsapi.h,聲明引擎中的所用到的記號,結構體,函數簽名等,這是使用 SpiderMonkey 所需的唯一一個介面檔案 ( 當然,jsapi.h 中不可能定義所有的介面,這些檔案在 jsapi.h 頭部引入 jsapi.h,如果對 C 語言的介面,標頭檔引入方式不熟悉的讀者,請參閱相關資料 )。

清單 2. 全域變數聲明

 /* 全域變數的類聲明 */ static JSClass global_class = {     "global",     JSCLASS_GLOBAL_FLAGS,     JS_PropertyStub,     JS_PropertyStub,     JS_PropertyStub,     JS_PropertyStub,     JS_EnumerateStub,     JS_ResolveStub,     JS_ConvertStub,     JS_FinalizeStub,     JSCLASS_NO_OPTIONAL_MEMBERS  }; 

JSClass 是一個較為重要的資料結構,定義了 JavaScript 對象的基本結構 ---“類”,這個類可以通過 SpiderMonkey 引擎來執行個體化為對象。JS_PropertyStub 是 JS_PropertyOp 類型的變數,這裡的 JS_PropertyStub 是為了提供一個預設值。JS_PropertyOp 可以用做對象的 setter/getter 等的,這些內容我們將在後邊的章節詳細討論。

清單 3. 錯誤處理函數

 /* 錯誤處理函數,用於回調,列印詳細資料 */  void report_error(JSContext *cx,  const char *message, JSErrorReport *report){     fprintf(stderr, "%s:%u:%s\n",      report->filename ? report->filename : "<no filename>",             (unsigned int) report->lineno,             message);  } 

定義好這些結構之後,我們需要執行個體化這些結構,使之成為記憶體對象,流程如下:

清單 4. 主流程

int main(int argc, char *argv[]){     JSRuntime *runtime;     JSContext *context;     JSObject *global;  // 建立新的運行時 8M     runtime = JS_NewRuntime(8L * 1024L * 1024L);     if (runtime == NULL){         return -1;     }  // 建立新的上下文    context = JS_NewContext(runtime, 8*1024);     if (context == NULL){         return -1;     }  //     JS_SetOptions(context, JSOPTION_VAROBJFIX);     // 設定錯誤回呼函數 , report_error 函數定義如上 JS_SetErrorReporter(context, report_error);  // 建立一個新的 JavaScript 對象    global = JS_NewObject(context, &global_class, NULL, NULL);     if (global == NULL){         return -1;     }  // 執行個體化 global, 加入對象,數組等支援    if (!JS_InitStandardClasses(context, global)){         return -1;     }  //  // 使用 global, context 等來完成其他動作,使用者定製代碼由此開始 //  // 釋放內容物件    JS_DestroyContext(context);  // 釋放運行時環境    JS_DestroyRuntime(runtime);  // 停止 JS 虛擬機器    JS_ShutDown();  return 0;  } 

使用者自己的代碼從上邊代碼中部注釋部分開始,使用者代碼可以使用此處的 context 對象及預設過一定屬性,方法的 global 對象。

執行 JavaScript 代碼

執行 JavaScript 程式碼片段

執行 JS 最簡單的方式,是將指令碼作為字串交給引擎來解釋執行,執行完成之後釋放臨時的指令碼對象等。SpiderMonkey 提供一個 JS_EvaluateScript 函數,原型如下:

清單 5. 執行 JS 代碼的函數原型

 JSBool JS_EvaluateScript(JSContext *cx, JSObject *obj,     const char *src, uintN length, constchar *filename,     uintN lineno, jsval *rval); 

使用這個函數,需要提供上下文,全域變數,字串形式的指令碼,指令碼長度及傳回值指標,指令碼名和行號參數可以填空值 ( 分別為 NULL 和 0)。如果函數返回 JS_TRUE,表示執行成功,執行結果存放在 rval 參數中,否則執行失敗,rval 中的值為 undefined。我們可以具體來看一個例子:

清單 6. 執行 JS 程式碼片段

char *script = "(function(a, b){return a * b;})(15, 6);";  jsval rval;  status = JS_EvaluateScript(context, global, script, strlen(script)\         , NULL, 0, &rval);  if (status == JS_TRUE){     jsdouble d;     JS_ValueToNumber(context, rval, &d);     printf("eval result = %f\n", d);  } 

執行結果為:

 eval result = 90.000000 

編譯 JavaScript 代碼

通常,我們可能會多次執行一段指令碼,SpiderMonkey 可以將指令碼編譯成 JSScript 對象,然後可以供後續的多次調用。現在來看一個例子,使用 C 代碼編譯一個 JavaScript 指令碼,然後運行這個指令碼。

清單 7. 從檔案載入並執行指令碼

 JSBool evalScriptFromFile(JSContext *cntext, constchar *file){     JSScript *script;     JSString *jss;     JSBool status;     jsval value;     //get the global object     JSObject *global = JS_GetGlobalObject(context);     //compile the script for further using     script = JS_CompileFile(context, global, file);     if (script == NULL){         return JS_FALSE;     }     //execute it once     status = JS_ExecuteScript(context, global, script, &value);     jss = JS_ValueToString(context, value);     printf("eval script result is : %s\n", JS_GetStringBytes(jss));     //destory the script object     JS_DestroyScript(context, script);     return status;  } 

這裡傳遞給函數 evalScriptFromFile的 JSContext* 參數為外部建立好的 Context 對象,建立的方法參看上一節。

清單 8. 執行

 JSBool status = evalScriptFromFile(context, "jstest.js");  if (status == JS_FALSE){         fprintf(stderr, "error while evaluate the script\n");  } 

假設我們將如下指令碼內容儲存進一個指令碼 jstest.js:

清單 9. jstest.js 指令碼內容

varPerson = function(name){     var _name_ = name;     this.getName = function(){         return _name_;     }     this.setName = function(newname){         _name_ = newname;     }  }  varjack = new Person("jack");  jack.setName("john");  // 最後一句將作為指令碼的執行結果返回給 C 代碼 jack.getName(); 

jack 對象的名字現在設定為了”john”, 指令碼的最後一條語句的值將作為指令碼的傳回值返回到 C 代碼處,並列印出來:

 eval script result is : john 

回頁首

C 與 JavaScript 的互動

C 程式調用 JavaScript 函數

由於兩者的資料類型上有較大的差異,因此無法直接從 C 代碼中調用 JavaScript 代碼,需要通過一定的轉化,將 C 的變數轉換為 JavaScript 可以設別的變數類型,然後進行參數的傳遞,傳回值的處理也同樣要經過轉換。

我們在 JavaScript 中定義一個函數 add,這個函數接受兩個參數然後返回傳入的兩個參數的和。定義如下:

清單 10. JavaScript 版本的 add

function add(x, y){     return x + y;  } 

然後,我們在 C 語言中根據名稱調用這個 JS 函數:

清單 11. 從 C 代碼中調用 JavaScript 函數

 JSBool func_test(JSContext *context){     jsval res;     JSObject *global = JS_GetGlobalObject(context);     jsval argv[2];     //new 2 number to pass into the function "add" in script     JS_NewNumberValue(context, 18.5, &res);     argv[0] = res;     JS_NewNumberValue(context, 23.1, &res);     argv[1] = res;     JS_CallFunctionName(context, global, "add", 2, argv, &res);     jsdouble d;     //convert the result to jsdouble     JS_ValueToNumber(context, res, &d);     printf("add result = %f\n", d);     return JS_TRUE;  } 

這裡需要注意的是,JS_CallFunctionName 函數的參數列表:

清單 12. JS_CallFunctionName 原型

 JSBool  JS_CallFunctionName(JSContext *cx, JSObject *obj,  const char *name, uintN argc, jsval *argv, jsval *rval); 

表 2. JS_CallFunctionName 參數列表含義

名稱

類型

類型描述

cx

JSContext *

上下文定義

obj

JSObject *

調用該方法的對象

name

const char *

函數名

argc

uintN

函數參數個數

argv

jsval *

函數實際參數形成的數組

rval

jsval *

傳回值

參數中的 argv 是一個 jsval 形成的數組,如果直接傳遞 C 類型的值,則很容易出現 core dump(Linux 下的段錯誤所導致 ),因此,需要 JS_NewNumberValue 函數轉換 C 語言的 double 到 number( 原因見對象轉換小節 )。

JavaScript 程式調用 C 函數

從 JS 中調用 C 函數較上一節為複雜,我們來看一個較為有趣的例子:SpiderMonkey 中原生的 JavaScript 的全域變數中沒有 print 函數,我們可以使用 C 的 printf 來實現這個功能。我們定義了一個函數 print, print 使用 logging 函數,而 logging 函數是定義在 C 語言中的,接受一個字串作為參數,列印這個字串到標準輸出上 :

清單 13. JavaScript 調用 C 函數

 //log user log in information  logging("user jack login on 2010/7/6");  //user do nothing else  nothing();  //log user log out information  logging("user jack logout on 2010/7/7");  function print(){     for (vari = 0; i < arguments.length; i++){         logging(arguments[i]);     }  }  print("hello", "all", "my", "friend"); 

在 C 語言中,我們定義 logging 函數和 nothing 函數的原型如下:

清單 14. C 函數的實現

 /**  * define an exposed function to be used in scripts  * print out all the incoming arguments as string.  */  static JSBool  logging(JSContext *context, JSObject *object, uintN argc,         jsval *argv, jsval *value){     int i = 0;     JSString *jss;     for(i = 0; i < argc; i++){        jss = JS_ValueToString(context, argv[i]);        printf("message from script environment : %s\n", \                JS_GetStringBytes(jss));     }     return JS_TRUE;  }  /**  * define an exposed function to be used in scripts  * do nothing but print out a single line.  */  static JSBool  nothing(JSContext *context,  JSObject *object, uintN argc, jsval *argv, jsval *value)  {     printf("got nothing to do at all\n");     return JS_TRUE;  } 

從函數的簽名上可以看出,C 中暴露給 JS 使用的函數,參數的個數,及對應位置上的類型,傳回值都是固定的。所有的從 C 中暴露給 JS 的函數都需要“實現這個介面”。

定義好了函數之後,還需要一些設定才能在 JS 中使用這些函數。首先定義一個 JSFunctionSpec 類型的數組,然後通過 JS_DefineFunctions 將這些函數放到 global 對象上,然後在 JS 代碼中就可以訪問上邊列出的 C 函數了。具體步驟如下:

清單 15. 註冊函數列表

static JSFunctionSpec functions[] = {     {"logging", logging, LOG_MINARGS, 0, 0},     {"nothing", nothing, NOT_MINARGS, 0, 0},     {0, 0, 0, 0, 0}  };     //define function list here     if (!JS_DefineFunctions(context, global, functions)){         return -1;  } 

運行結果如下:

 message from script environment : user jack login on 2010/7/6  got nothing to do at all  message from script environment : user jack logout on 2010/7/7  message from script environment : hello  message from script environment : all  message from script environment : my  message from script environment : friend 

在 C 程式中定義 JavaScript 對象

在 SpiderMonkey 中,在 JavaScript 中使用由 C 語言定義的對象較為複雜,一旦我們可以定義對象,使得兩個世界通過 JS 互動就變得非常簡單而有趣,很容易使用這樣的方式來定製我們的應用,在系統發布之後仍然可以輕鬆的修改系統的行為。

首先,我們要定義好基本的資料結構,即我們要暴露給 JS 世界的對象的屬性,結構;然後,使用 JSAPI 定義這個對象的屬性;然後,使用 JSAPI 定義對象的方法;最後,創佳這個對象,並綁定其屬性工作表和方法列表,放入全域對象。

假設我們有這樣一個資料結構,用來表述一個人的簡單資訊 :

清單 16. PersionInfo 結構

 typedef struct{     char name[32];     char addr[128];  }PersonInfo; 

定義屬性工作表為枚舉類型:

清單 17. 屬性工作表

enum person{     NAME,     ADDRESS }; 

我們需要將 C 語言原生的資料結構定義為 JSAPI 所識別的那樣:

清單 18. 屬性定義

 //define person properties  JSPropertySpec pprops[] = {     {"name", NAME, JSPROP_ENUMERATE},     {"address", ADDRESS, JSPROP_ENUMERATE},     {0}  }; 

清單 19. 方法定義

 //define person methods  JSFunctionSpec pfuncs[] = {     {"print", printing, 0},     {"getName", getName, 0},     {"setName", setName, 0},     {"getAddress", getAddress, 0},     {"setAddress", setAddress, 0},     {0}  }; 

清單 20. 類定義

 //define person class (JSClass)  JSClass pclass = {     "person", 0,     JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,     JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub  }; 

一旦這些基本資料定義好 (pfuncs 數組中的 getter/setter 的實現比較簡單,這裡由於篇幅不列出代碼,感興趣的朋友可以參看附錄 ),我們就可以執行個體化它,並將其放入上下文中,使得 JS 代碼可以訪問。

清單 21. 定義對象及對象的屬性,方法

    JSObject *person;     //define the object     person = JS_DefineObject(\             context, global, "person", &pclass, 0, JSPROP_ENUMERATE);     //install the properties and methods on the person object     JS_DefineProperties(context, person, pprops);     JS_DefineFunctions(context, person, pfuncs); 

這樣,在 JavaScript 代碼中,我們就可以通過 person 這個標識符來訪問 person 這個對象了:

清單 22. 測試指令碼

 //undefined of course  person.print();  //person.name = "abruzzi";  //person.address = "Huang Quan Road";  person.setName("Desmond");  person.setAddress("HuangQuan Road");  //print is global function, access properties directly  print("person name = " + person.name);  print("person address = " + person.address);  person.print();  (function(){     //using getter/setter to access properties     return person.getName() + " : " + person.getAddress();  })(); 

對運行結果如下:

 name : undefined address : undefined person name = Desmond  person address = HuangQuan Road  name : Desmond  address : HuangQuan Road  eval script result is : Desmond : HuangQuan Road 

回頁首

結束語

本文中詳細討論了如何使用基於 C 的 JavaScript 引擎:SpiderMonkey 的用法。包括最基本的代碼模板,C 代碼與 JavaScript 代碼之間的互動,以及在 C 代碼中定義 JavaScript 對象等內容,使用這些基本概念,很容易將實現應用程式的指令碼化。在實際的應用中,可以將應用程式的部分組件 ( 提供給使用者自訂的組件 ) 暴露給 JavaScript 訪問,或者在 JavaScript 指令碼中提供函數的存根 ( 僅僅定義函數的原型 ),使用者可以通過實現這些函數的具體邏輯,來實現指令碼化。

回頁首

下載

描述

名字

大小

下載方法

範例代碼

jsfun.zip

8KB

HTTP

關於下載方法的資訊

參考資料

學習

  • SpiderMonkey 官方網站,包括 API 函數簽名,完整的文檔等。
  • w3c 的一個簡易的 JavaScript教程,介紹用戶端的 JavaScript 的基本用法。
  • 一篇介紹 SpiderMonkey 的文章,介紹 SpiderMonkey 基本的用法。
  • 在 developerWorks Linux 專區尋找為 Linux 開發人員(包括 Linux 新手入門)準備的更多參考資料,查閱我們 最受歡迎的文章和教程。
  • 在 developerWorks 上查閱所有 Linux 技巧和 Linux 教程。
  • 隨時關注 developerWorks 技術活動和 網路廣播。

討論

  • 歡迎加入 developerWorks 中文社區。

關於作者

邱俊濤,畢業於昆明理工大學電腦科學與技術專業,對機械控制、電子、人工智慧、函數式編程等領域有濃厚的興趣,對電腦科學的底層比較熟悉。喜歡 C/JAVA/Python/JavaScript 等語言。現就職於雲電同方研發中心。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.