跨越語言的邊界 – 淺談 JS API 與 XPConnect

跨越語言的邊界 – 淺談 JS API 與 XPConnect

開發 Mozilla 專案主要會使用兩種程式語言:C++ 與 JavaScript。第一次接觸到這份代碼時,我心中就產生了一個疑問:C++ 是強型態、靜態編譯的語言,而 JavaScript 則是弱型態、直譯式的語言,這兩種特性截然不同的程式語言該如何溝通呢?以下讓我們來分析一下 C++ 與 JavaScript 間若要相互溝通需要打通哪些關節。

首先要解決的是基本資料型別以及命名空間的問題,若想從 C++ 中存取 JavaScript 的執行期資訊,就需要使用 JavaScript Engine(以下簡稱 JS Engine)所提供的 JS API,這個作法就等於是直接接觸 JS Engine 所維護的資料結構來模擬對應的 JavaScript 語法。

由於我們是向 JS Engine 取得資料,而一個 JS Engine 上同時會載入複數個 JavaScript 腳本,命名空間的差異會讓相同的一段 var foo = a+b; 在不同腳本上執行時產生出不同的結果,所以你需要告訴 JS Engine 對應程式的文本(context)才能得出正確的結果,因此大多數的 JS API 都需要使用者提供對應的 JSContext。此外,由於 JavaScript 的弱型態特性,在 JS API 中提供了一個通用的變數型別 jsval 來描述,直到真正需要取出變數內容時再透過的 helper function 取得實際內容。更深入的 JS API 說明可以參考 MDN 上頭的 JS API User Guide [1]。以下來看一段範例程式感受一下 JS API 的用法:

//Javascript: var x = y.myprop; //C++: JSContext *cx = ... // get JS context from somewhere jsval x, y; // get variable y if (!JS_GetProperty(cx, JS_GetGlobalForScopeChain(cx), "y", &y)) {     JS_ReportError(cx, "variable y is undefined.");     return JS_FALSE; } // make sure variable y is a JS object if (JSVAL_IS_PRIMITIVE(y)) {     JS_ReportError(cx, "variable y must be an object.");     return JS_FALSE; } // get y.myprop and store the value in x if (!JS_GetProperty(cx, JSVAL_TO_OBJECT(y), "myprop", &x)) {     JS_ReportError(cx, "fail to get property on variable y.");     return JS_FALSE; }

這兩段代碼所作的事情一模一樣,但 C++ 版本多出許多型別檢查與異常處理的步驟,所以你會發現離開 JS Engine 的保護傘寫起程式來特別的囉唆。更囉唆多的 JS API 範例可以參考 JS API Cookbook [2]。

因此,光是能在 C++ 存取 JS Engine 還不夠 developer-friendly,而 Mozilla 專案中最重要的軟體元件開發模式 XPCOM [3],若能以 XPCOM 元件的形式任意引用 C++ 或 JavaScript 的代碼,就能以更高階、更方便的寫法來達成 C++ 與 JavaScript 間的溝通,這時候就該讓 XPConnect 登場了!XPConnect 就是基於 JS API,提供在Javascript實作與引用 XPCOM 元件的各種功能,我們用以下範例說明 XPConnect 運作模式:

//Javascript: var obs = Component.classes["@mozilla.org/observer-service;1"]                    .getService(Component.interfaces.nsIObserverService); obs.addObserver(someObserver, "some-topic", false);

第一步要解決的問題是:在 JavaScript 中如何搜索到指定界面的實作?藉由 Components.classes 可以從 Contract ID 找出對應的 Concrete class,而透過這個 Concrete class 就可以用來取得物件的實體,並且轉型成所需的界面。等等!轉型在弱型態的 JavaScript 有什麼作用?對於 JavaScript object 而言的確沒有所謂型態,但是 XPCOM 在使用時必須透過此型別快速辨認對應物件有實作所需的界面,就能儘早確認取出的 XPCOM 元件是否符合需求。早期發現、早期治療,不然等實際使用物件時才發現 function 找不到就很難除錯了。

第二步就是使用該界面提供的 attribute 與 function,我們在第一步所取得的物件實際上是個 wrapper  [4],這個 wrapper 的作用是用來檢查存取的合法性以及彌補 C++ 與 JavaScript 對物件操作的差異,比如說 JavaScript 能動態增加 object property,這個操作就只會改變 wrapper 而不會影響到原本的 XPCOM 物件;而傳入參數型別、個數是否符合也會由 XPConnect 一併檢查 [5]。讓 XPCOM 能夠融入原本 JavaScript 的語法,使用起來就跟 JavaScript object 一樣簡單。

若是從 C++ 引用由 JavaScript 實作的 XPCOM 元件,同樣也會由 XPConnect 將 JavaScript object 包裝成標準的 C++ 物件。有了這麼方便的跨語言機制,就可以根據實作的需求挑選適合的語言撰寫,是不是覺得程式寫來格外輕鬆了呢?

References:

  1. https://developer.mozilla.org/en-US/docs/SpiderMonkey/JSAPI_User_Guide
  2. https://developer.mozilla.org/en-US/docs/SpiderMonkey/JSAPI_Cookbook
  3. https://developer.mozilla.org/en-US/docs/XPCOM
  4. https://developer.mozilla.org/en-US/docs/XPConnect_wrappers
  5. https://developer.mozilla.org/en-US/docs/XPIDL#Types

掌握最新 Firefox, Firefox OS 相關訊息

 

加入 Mozilla Taiwan 臉書粉絲團 

加入 Mozilla Taiwan  G+ 

瀏覽 Mozilla Taiwan 部落格 

官網 mozilla.com.tw