Scriptable Plugin (NPAPI&NPRuntime)

原文地址http://rintarou.dyndns.org/2010/04/23/scriptable-plugin-%E6%8E%A2%E8%A8%8E-20090408/

 


本篇文章在探討 NPAPI 與 NPRuntime 的設計,並非 Plugin 教學。

當時因為看到公司內部寫出來的 Plugin 問題不少,而且網路上說明太少,特地寫來給大家看的~

故本篇沒有詳細介紹每個 API 的使用與功能,請見諒囉!

This article was written in 2009/04/08.

 

NPAPI & NPRuntime 簡介

Netscape Plugin Application Programming Interface (NPAPI)

NPAPI 原本是由 Netscape 所制定的一組單純的 C Plugin API,起初是無法支援 Scriptability;於是到了 2004 年底時,各家 Browser (IE, Opera, Mozilla 等) 都同意支援 NPRuntime 延伸 API 以支援 Scriptability,所以目前若是想寫 Plugin 則應該以 NPRuntime API 才能跨不同的 Browsers。

 

Plugin Life Cycle

 

上面的 Sequence Diagram 說明了 Browser 與 Plugin 之間的運作過程:

  1. Browser lookup Plugin (.so, .dll) and load it.
  2. Browser 呼叫 Plugin 的 NP_Initialize() 來交換彼此所需的 API Function Pointers。
    • 將 Browser Side 的 NPN_ API function table (NPNetscapeFuncs *aNPNFuncs) 傳給 Plugin (Binding)。
    • Plugin 應將其自身所定義好的 NPP  API functions 填入 NPPluginFuncs *aNPPFuncs 中,好讓 Browser 得到 Plugin Side 的 Function pointers。
  3. Browser 呼叫 Plugin 的 NP_GetValue() 來得到 Plugin 的資訊,例如:版本資訊與是否支援 Scriptability 等。
  4. Browser 在網頁中發現 Plugin 所支援的 Mime Type 時,呼叫 Plugin 的 NPP_New() 來建立新的 Plugin instance 來處理。
  5. 當網頁被 Unload 前,Browser 則會呼叫 Plugin NPP_Destroy() 來通知 Plugin 應 Destroy 所對應的 Plugin instance 。
  6. 當 Browser 程式結束前會呼叫 Plugin 的 NP_Shutdown() 做 Destruction,結束整個 Plugin Life Cycle 。

 

以下為  API 宣告:

 

NPNetscapeFuncs (NPN_XXXXX API)

NPNetscapeFuncs 是一個 Function pointer table,是 Browser 傳給 Plugin 使用的 NPN_XXXXX API。

宣告如下:

 

 

NPPluginFuncs (NPP_XXXX API)

NPPluginFuncs 也是一個 Function pointer table,是由 Plugin 傳回給 Browser 使用的 NPP_XXXXX API。

宣告如下:

 

Plugin Instance Construction and Destruction

當 Browser 在 HTML 中發現 Plugin 所對應的 Mime Type 時,會呼叫 NPP_New() 來向 Plugin 要求一個 Plugin Instace 服務。

NPP_New() 定義如下:

 

NPP 即為 Plugin Instance 資料結構,由 Browser 所建立,透過 NPP_New() 傳送給 Plugin。

NPP 的 資料結構很簡單,僅包含兩個 void pointer:

  1. void *pdata : Plugin Private Data
  2. void *ndata : Browser Private Data

宣告如下:

 

而 Browser 在某個 Page 被 Unload 之前,則會呼叫 NPP_Destroy() 來通知 Plugin 結束所對應的 Plugin Instance。

Scriptability

Scriptability 就是讓 JavaScript 可以將 Plugin 當作 JavaScript Object 來使用,而 NPRuntime 定義了 NPObject 與 NPClass 兩個結構來建立 Browser 能夠了解的 Scriptable Object 。

Multiple NPObject Instances

該注意的一點是,NPObject 本身也是需要支援 Multiple Instance,原因很簡單,因為 Plugin Instance 都應該擁有自己的 NPObject,若是 NPObject 不設計成 Multiple Instance,就得所有 Plugin Instance 「共用」一組 NPObject,將會帶來很多擴充性上的困難。

 

Scriptable Object Model (NPObject & NPClass design with UML)

以下是個人從 NPRuntime 設計中理解出的 Scriptable Object Model (名字取不好,多見諒。)

 

What is NPClass?

NPClass 是一組 Interface (function pointer table),代表某個 NPObject 在建立 Instance 時所需要的動作(ex: Constructor/Desctructor),也就是說 Browser 只透過 NPClass 所指定的 Methods 來建立新的 NPObject Instance。舉例來說,當 Browser 透過 NPP_GetValue() 來向 Plugin 要一個 Property 時,Plugin 可以傳回一個 NPObject 給 Browser ,讓 Browser 知道其實這個 Plugin Property 其實是一個 Scriptable Object。

NPClass 其實就是所謂的 Marshaling Functions,這個原本由 RPC 發展出來的方法已經在很多地方都可以看到,幾乎只要是 Virtual Machine 相關的系統都會用這個方式來達到模擬 Calling Convention 的目的。
不過也有例外的,像是 Mozilla XPCOM 的 xptcall 就是直接從 register/stack 來做 Marshaling 的動作。

PluginObject 是我們實際上想建立的 Object,它應該具有我們想要的 Custome Properties 與 Methods,而 PluginObject 所擁有的 NPClass 其實就是 PluginClass ,也是由我們設計的,因為只有設計者才知道 Plugin Object 應該如何 construct/destruct/etc…)。

當 Browser 需要建立 NPObject Instance 時,會呼叫 NPObject→NPClass→allocate(),也就會呼叫到 PluginClass→pluginAllocate(),我們就可以 new PluginObject() 傳回給 Browser 了。簡單的說,Browser 想要建立或是存取任何 PluginObject,都得透過 PluginClass 中的 API,Browser 是無法直接存取 PluginObject 的 Custom Property/Methods 。

雖然 NPRuntime 的呼叫都是 C API,但是實際上這組 API 想完成的事就是上面的 Scriptable Object Model。若只是了解 Call Flow 是不夠的,要能「讀出」原來設計這組 API 的人在「想」的是什麼。

NPClass 與 NPObject 的 C 宣告如下:

 

When should NPObject be created?

當 NPP_New() 要求建立 Plugin Instance ,就需要建立我們的 PluginObject(which is a NPObject) Instance,在此同時,應該直接以NPN_CreateObject() 來建立相對應的 NPObject,因為 NPObject 是由 Browser 來主動 Allocate/Free Memory,所以 reference count 也會紀錄在 NPObject 中。

以下為使用 NPN_CreateObject() 來建立 NPObject 的範例:
 
NPN_CreateObject() 是由 Plugin 向 Browser 要求建立一個 NPObject,此 NPObject 的 NPClass 就是我們所指定的 PluginClass。

若是 PluginClass 中有指定 allocate(),則 Browser 會使用 PluginClass -> allocate() 來做來替 NPObject allocate Memory ,否則 Browser 會以 malloc() 來 Allocate NPObject。傳回的 NPObject 的 reference count 會變成 1。

這裡有一件很重要的技巧,PluginClass 中的 pluginAllocate() 應該要 new PluginObject 傳回給 Browser,因為 PluginObject 「繼承自」 NPObject, 從 Browser 的角度來看,PluginObject 就只是個 NPObject;但是對於 Plugin 來說,instance→pdata 所存放的其實是 PluginObject;之後 Browser 呼叫 PluginClass 中的 Methods 時,我們只需要取得 instance→pdata 就可以當做是 PluginObject,並直接存由 Plugin 自己定義的 Custom Properties/Methods 了。如此一來,就不用一堆 Variable 指來指去的了,這是簡化支援 Multiple Instance 的一個重點。

這樣的技巧在 Apple Objective-C 與 Object-Oriented Language (ex: C++ vtable) 裡是很常見的,可惜的是我們目前的實作完全沒有 OO 的思考。
instance->pdata 反正是個 void *,可以任意 casting 成任一種 type,Browser 與 Plugin 就像一個中國各自表述啦~

 

Browser ask for NPObject

Browser 在 NPP_New() 建立 Plugin Instance 之後,還會以

NPP_GetValue(npp, NPPVpluginScriptableNPObject, void* value);

來詢問 Plugin 是否支援 Scriptable,此時再把我們先前建立好的 PluginObject (stored in instance->pdata) 透過 value 傳回給 Browser 即可。

範立如下:

 
NPRuntime API中規定,在傳回 NPObject 之前,應該先以 NPN_RetainObject() 來增加 NPObject.refCount,這對 JavaScript Engine 的 Garbage Collection 機制很重要,千萬別忘了。

How NPObject was used by JavaScript?

2 Types of Marshaling Functions (Method/Property)

在上面建立完 Scriptable NPObject 後,等於是建立了一個相對應的 JavaScript Object 。於是 JavaScript 可以對 JavaScript Object 做其它的存取動作,而這些動作則被對應到 NPObject 的兩類 Marshaling Functions:

(提醒一下:NPObject->_class 就是 NPClass )

  1. Method 呼叫

     

     
  2. Property 存取

     

     

從以下範例說明會比較清楚:

 
從 ECMAScript 的角度說明如下:
  • myPlugin : "myPlugin" 是一個 JavaScript Object 的名字(Identifier),之後可以將 myPlugin 想像成 NPObject。(實際上 JavaScript VM 內部的對應要複雜許多)
  • fooMethod : "fooMethod" 是我們 Plugin 所提供的 Method 的名字(Identifier),則 myPlugin.fooMethod(); 會轉換成對 NPObject 內 NPClass 的 function call ,動作如下:
    1. 透過 hasMethod(NPObject, NPIdentifier of "fooMethod") 詢問 Plugin 是否提供名稱為 "fooMethod" 的 Method,若有則到 2.
    2. 透過 invokeMethod(NPObject, NPIdentifier of "fooMethod", …) 來傳送參數給 Plugin,而 Plugin 則可由 NPIdentifier 得知 Browser 希望呼叫的 method 為何,再去執行所對應的功能,最後再傳回值 (result)。
  • fooProperty : "fooProperty" 是我們 Plugin 所提供的 Property 的名字(Identifier),則 myPlugin.fooProperty = "hello world"; 會轉換成為以下動作:
    1. 透過 hasProperty(NPObject, NPIdentifier of "fooProperty"); 詢問 Plugin 是否有提供名稱為 "fooProperty" 的 Property,若有則到 2.
    2. 透過 setProperty(NPObject, NPIdentifier of "fooProperty", "hello world"); 要求 Plugin 執行將 fooProperty 的值更改為 "hello world" 的動作。

以上說明著動在流程上,細節上並非完全正確,因為 JavaScript (ECMAScript) 內部有許多針對 Objects, Properties, Attributes 等細節,可以說上三天三夜了吧!

 

NPVariant (Parameters Serialization between JavaScript and C)

在 Marshaling Functions 中,會以 NPVariant 來傳送真正的參數資料。

NPVariant 就是參數的 Serialized Data Type 。

 

 

Data Type Mapping Between JavaScript and NPVariant

NPVariant 所封裝的資料型態會對應到 JavaScript 資料型態。

對應如下:

JavaScriptC (NPVariant with type:)
undefinedNPVariantType_Void
nullNPVariantType_Null
BooleanNPVariantType_Bool
NumberNPVariantType_Double or NPVariantType_Int32
StringNPVariantType_String
ObjectNPVariantType_Object

 

Marshaling Macro

為了方便 NPVariant 與 JavaScript 間的資料轉換, NPRuntime 也定義了一組轉換的 Macro 方便程式設計。

 

Why need NPVariant (Serialization) ?

在 JavaScript 中,使用者可以任意撰寫任何 function,而這些 function 的參數個數,型態,排列順序等,都是任意的;我們不可能寫出一個 C function 來對應到所有的 JavaScript function,C function 是 compile time 時就必須決定參數個數,型態與順序;因此必須要透過 Marshaling (Serialization) 的方式來取得 JavaScript 的參數後,再轉換成 C 語言中相對應的資料型態來處理。

NPIdentifier

NPObject 的 Method 與 Property 的皆是由 NPIdentifier 來指定,NPIdentifier 對於相同名稱的 Method 或是 Property 會有一個 Unique 值。而 NPIdentifier 的值是由 Browser 所提供,也就是說 Browser 內部有一個 (Hash) Table 來儲存所有的 NPIdentifier 。

 
Browser 另外還提供了 NPIdentifier 的轉換函數,供 Plugin 方便使用。

Why use NPIdentifier?

有 NPIdentifier 這樣的設計主要有兩個原因:

  1. Less Memory Cost
    對於許多 Object 來說都有相同名稱的 Method 或是 Property,若是將這些「名稱字串」全都儲存在 Object 的 Instance 中,對於 Memory 的消耗實在是一種浪費。
  2. Fast Lookup (ECMAScript Identifer Resolution)
    對於 Browser 或是 Plugin 在 JavaScript 執行時在 Lookup Object 的動作時,能夠以 Lookup NPIdentifier 來取代 Name String Compare,可以大大增加 Lookup 的速度。

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值