原帖: http://wenku.baidu.com/link?url=dNdAGHq6ty0HQxO6iA8BpD1IzVv2-eP6R_zCdfsm2tfugvGwyxlzmlePjF8mfgYCP6vhxouLHNt4KfKFCEZVPCnSOY6Cx5f9b9hE9U-8RA7
NPAPI &NPRuntime 簡介
Netscape PluginApplication Programming Interface (NPAPI)
NPAPI 原本是由 Netscape 所制定的一組單純的 C Plugin API,起初是無法支援 Scriptability;於是到了 2004 年底時,各家 Browser (IE,Opera, Mozilla 等) 都同意支援 NPRuntime 延伸 API 以支援 Scriptability,所以目前若是想寫 Plugin 則應該以 NPRuntime API 才能跨不同的 Browsers。
Plugin LifeCycle
上面的 SequenceDiagram 說明了 Browser 與 Plugin 之間的運作過程:
1. Browser lookup Plugin (.so, .dll) andload it.
2. Browser 呼叫 Plugin 的 NP_Initialize()來交換彼此所需的 API Function Pointers。
1. 將 Browser Side 的 NPN_API functiontable (NPNetscapeFuncs *aNPNFuncs) 傳給 Plugin(Binding)。
2. Plugin 應將其自身所定義好的 NPP API functions填入 NPPluginFuncs *aNPPFuncs 中,好讓 Browser 得到 Plugin Side 的 Functionpointers。
Browser 呼叫 Plugin 的 NP_GetValue() 來得到 Plugin 的資訊,例如:版本資訊與是否支援 Scriptability 等。
Browser 在網頁中發現 Plugin 所支援的 Mime Type 時,呼叫 Plugin 的 NPP_New() 來建立新的 Plugin instance來處理。
當網頁被 Unload 前,Browser 則會呼叫 PluginNPP_Destroy() 來通知 Plugin 應 Destroy 所對應的 Plugin instance。
當 Browser 程式結束前會呼叫 Plugin 的 NP_Shutdown() 做 Destruction,結束整個 Plugin LifeCycle 。
以下為 API 宣告:
char*NP_GetMIMEDescription() // Unix only NPError NP_GetValue(void*, NPPVariable,void* out) NPError NP_Initialize(NPNetscapeFuncs*, NPPluginFuncs*) NPErrorOSCALL NP_Shutdown()
NPNetscapeFuncs(NPN_XXXXX API)
NPNetscapeFuncs 是一個 Functionpointer table,是 Browser 傳給 Plugin 使用的 NPN_XXXXX API。
宣告如下:
typedef struct_NPNetscapeFuncs { uint16 size; uint16 version; NPN_GetURLProcPtr geturl;NPN_PostURLProcPtr posturl; NPN_RequestReadProcPtr requestread;NPN_NewStreamProcPtr newstream; NPN_WriteProcPtr write;NPN_DestroyStreamProcPtr destroystream; NPN_StatusProcPtr status;NPN_UserAgentProcPtr uagent; NPN_MemAllocProcPtr memalloc; NPN_MemFreeProcPtrmemfree; NPN_MemFlushProcPtr memflush; NPN_ReloadPluginsProcPtr reloadplugins;NPN_GetJavaEnvProcPtr getJavaEnv; NPN_GetJavaPeerProcPtr getJavaPeer;NPN_GetURLNotifyProcPtr geturlnotify; NPN_PostURLNotifyProcPtr posturlnotify;NPN_GetValueProcPtr getvalue; NPN_SetValueProcPtr setvalue; NPN_InvalidateRectProcPtrinvalidaterect; NPN_InvalidateRegionProcPtr invalidateregion;NPN_ForceRedrawProcPtr forceredraw; NPN_GetStringIdentifierProcPtrgetstringidentifier; NPN_GetStringIdentifiersProcPtr getstringidentifiers;NPN_GetIntIdentifierProcPtr getintidentifier; NPN_IdentifierIsStringProcPtridentifierisstring; NPN_UTF8FromIdentifierProcPtr utf8fromidentifier;NPN_IntFromIdentifierProcPtrintfromidentifier; NPN_CreateObjectProcPtrcreateobject; NPN_RetainObjectProcPtr retainobject; NPN_ReleaseObjectProcPtrreleaseobject; NPN_InvokeProcPtr invoke; NPN_InvokeDefaultProcPtrinvokeDefault; NPN_EvaluateProcPtr evaluate; NPN_GetPropertyProcPtrgetproperty; NPN_SetPropertyProcPtr setproperty; NPN_RemovePropertyProcPtrremoveproperty; NPN_HasPropertyProcPtr hasproperty; NPN_HasMethodProcPtrhasmethod; NPN_ReleaseVariantValueProcPtr releasevariantvalue;NPN_SetExceptionProcPtr setexception; NPN_PushPopupsEnabledStateProcPtrpushpopupsenabledstate; NPN_PopPopupsEnabledStateProcPtr poppopupsenabledstate;NPN_EnumerateProcPtr enumerate; NPN_PluginThreadAsyncCallProcPtrpluginthreadasynccall; NPN_ConstructProcPtr construct; NPN_ScheduleTimerProcPtrscheduletimer; NPN_UnscheduleTimerProcPtr unscheduletimer;NPN_PopUpContextMenuProcPtr popupcontextmenu; } NPNetscapeFuncs;
NPPluginFuncs(NPP_XXXX API)
NPPluginFuncs 也是一個 Functionpointer table,是由 Plugin 傳回給 Browser 使用的 NPP_XXXXX API。
宣告如下:
typedef struct_NPPluginFuncs { uint16 size; uint16 version; NPP_NewProcPtr newp;NPP_DestroyProcPtr destroy; NPP_SetWindowProcPtr setwindow;NPP_NewStreamProcPtr newstream; NPP_DestroyStreamProcPtr destroystream;NPP_StreamAsFileProcPtr asfile; NPP_WriteReadyProcPtr writeready;NPP_WriteProcPtr write; NPP_PrintProcPtr print; NPP_HandleEventProcPtr event;NPP_URLNotifyProcPtr urlnotify; JRIGlobalRef javaClass; NPP_GetValueProcPtrgetvalue; NPP_SetValueProcPtr setvalue; } NPPluginFuncs;
Plugin InstanceConstruction and Destruction
當 Browser 在 HTML 中發現 Plugin 所對應的 Mime Type 時,會呼叫 NPP_New() 來向 Plugin 要求一個 Plugin Instace 服務。
NPP_New() 定義如下:
#include<npapi.h> NPError NPP_New(NPMIMEType pluginType, NPP instance, uint16mode, int16 argc, char* argn[], char* argv[], NPSavedData* saved); NPErrorNPP_Destroy(NPP instance, NPSavedData **save);
NPP 即為 Plugin Instance資料結構,由 Browser 所建立,透過 NPP_New() 傳送給 Plugin。
NPP 的資料結構很簡單,僅包含兩個 void pointer:
1. void *pdata : Plugin Private Data
2. void *ndata : Browser Private Data
宣告如下:
typedef struct_NPP { void* pdata; void* ndata; } NPP_t; typedef NPP_t* NPP;
而 Browser 在某個 Page 被 Unload 之前,則會呼叫 NPP_Destroy() 來通知 Plugin 結束所對應的 Plugin Instance。
Scriptability
Scriptability 就是讓 JavaScript 可以將 Plugin 當作 JavaScriptObject 來使用,而 NPRuntime 定義了 NPObject 與 NPClass 兩個結構來建立 Browser 能夠了解的 ScriptableObject 。
Multiple NPObjectInstances
該注意的一點是,NPObject 本身也是需要支援 MultipleInstance,原因很簡單,因為 Plugin Instance 都應該擁有自己的 NPObject,若是 NPObject 不設計成 MultipleInstance,就得所有 Plugin Instance 「共用」一組 NPObject,將會帶來很多擴充性上的困難。
Scriptable ObjectModel (NPObject & NPClass design with UML)
以下是個人從 NPRuntime 設計中理解出的 ScriptableObject Model (名字取不好,多見諒。)
What is NPClass?
NPClass 是一組 Interface(function pointer table),代表某個 NPObject 在建立 Instance 時所需要的動作(ex:Constructor/Desctructor),也就是說 Browser 只透過 NPClass 所指定的 Methods 來建立新的 NPObjectInstance。舉例來說,當 Browser 透過 NPP_GetValue() 來向 Plugin 要一個 Property 時,Plugin 可以傳回一個 NPObject 給 Browser ,讓 Browser 知道其實這個 Plugin Property其實是一個 Scriptable Object。
NPClass 其實就是所謂的 Marshaling Functions,這個原本由 RPC 發展出來的方法已經在很多地方都可以看到,幾乎只要是 Virtual Machine相關的系統都會用這個方式來達到模擬 CallingConvention 的目的。
不過也有例外的,像是 Mozilla XPCOM 的 xptcall 就是直接從 register/stack 來做 Marshaling 的動作。
PluginObject 是我們實際上想建立的 Object,它應該具有我們想要的 CustomeProperties 與 Methods,而 PluginObject 所擁有的 NPClass 其實就是 PluginClass ,也是由我們設計的,因為只有設計者才知道 Plugin Object 應該如何construct/destruct/etc…)。
當 Browser 需要建立 NPObjectInstance 時,會呼叫 NPObject→NPClass→allocate(),也就會呼叫到PluginClass→pluginAllocate(),我們就可以 newPluginObject() 傳回給 Browser 了。簡單的說,Browser 想要建立或是存取任何 PluginObject,都得透過 PluginClass 中的 API,Browser 是無法直接存取 PluginObject 的 CustomProperty/Methods 。
雖然 NPRuntime 的呼叫都是 C API,但是實際上這組 API 想完成的事就是上面的 ScriptableObject Model。若只是了解 Call Flow 是不夠的,要能「讀出」原來設計這組 API 的人在「想」的是什麼。
NPClass 與 NPObject 的 C 宣告如下:
struct NPClass {uint32_t structVersion; NPAllocateFunctionPtr allocate; NPDeallocateFunctionPtrdeallocate; NPInvalidateFunctionPtr invalidate; NPHasMethodFunctionPtrhasMethod; NPInvokeFunctionPtr invoke; NPInvokeDefaultFunctionPtrinvokeDefault; NPHasPropertyFunctionPtr hasProperty;NPGetPropertyFunctionPtrgetProperty; NPSetPropertyFunctionPtr setProperty;NPRemovePropertyFunctionPtr removeProperty; NPEnumerationFunctionPtr enumerate;}; struct NPObject { NPClass *_class; uint32_t referenceCount; // Additionalspace may be allocated here by types of NPObjects }
When shouldNPObject be created?
當 NPP_New() 要求建立 Plugin Instance,就需要建立我們的 PluginObject(which is a NPObject) Instance,在此同時,應該直接以NPN_CreateObject() 來建立相對應的 NPObject,因為 NPObject 是由 Browser 來主動 Allocate/FreeMemory,所以 reference count 也會紀錄在 NPObject 中。
以下為使用 NPN_CreateObject() 來建立 NPObject 的範例:
#include<npruntime.h> NPObject *NPN_CreateObject(NPP npp, NPClass *aClass);NPError NPP_New(NPMIMEType pluginType, NPP instance, uint16 mode, int16 argc,char* argn[], char* argv[], NPSavedData* saved) { // Scripting functionsappeared in NPAPI version 14 if (browser->version >= 14) instance->pdata= NPN_CreateObject ((NPP) instance, (NP_Class *) PluginClass); }
NPN_CreateObject()是由 Plugin 向 Browser 要求建立一個 NPObject,此 NPObject 的 NPClass 就是我們所指定的 PluginClass。
若是 PluginClass 中有指定 allocate(),則 Browser 會使用 PluginClass-> allocate() 來做來替NPObject allocate Memory ,否則 Browser 會以 malloc() 來 AllocateNPObject。傳回的 NPObject 的 reference count會變成 1。
這裡有一件很重要的技巧,PluginClass 中的pluginAllocate() 應該要 newPluginObject 傳回給 Browser,因為 PluginObject 「繼承自」 NPObject,從 Browser 的角度來看,PluginObject 就只是個 NPObject;但是對於 Plugin 來說,instance→pdata 所存放的其實是 PluginObject;之後 Browser 呼叫 PluginClass 中的 Methods 時,我們只需要取得 instance→pdata 就可以當做是 PluginObject,並直接存由 Plugin 自己定義的 CustomProperties/Methods 了。如此一來,就不用一堆 Variable 指來指去的了,這是簡化支援 MultipleInstance 的一個重點。
這樣的技巧在 AppleObjective-C 與 Object-OrientedLanguage (ex: C++ vtable) 裡是很常見的,可惜的是我們目前的實作完全沒有 OO 的思考。
instance->pdata 反正是個 void *,可以任意 casting 成任一種 type,Browser 與 Plugin 就像一個中國各自表述啦~
Browser ask forNPObject
Browser 在 NPP_New() 建立 Plugin Instance之後,還會以
NPP_GetValue(npp,NPPVpluginScriptableNPObject, void* value);
來詢問 Plugin 是否支援 Scriptable,此時再把我們先前建立好的 PluginObject(stored in instance->pdata) 透過 value 傳回給 Browser 即可。
範立如下:
NPErrorNPP_GetValue(NPP instance, NPPVariable variable, void *value) { if (variable ==NPPVpluginScriptableNPObject) { void **v = (void **)value; PluginObject *obj =instance->pdata; if (obj) NPN_RetainObject((NPObject*)obj); *v = obj; returnNPERR_NO_ERROR; } return NPERR_GENERIC_ERROR; }
NPRuntime API 中規定,在傳回 NPObject 之前,應該先以 NPN_RetainObject()來增加 NPObject.refCount,這對 JavaScriptEngine 的 Garbage Collection 機制很重要,千萬別忘了。
How NPObject wasused by JavaScript?
2 Types ofMarshaling Functions (Method/Property)
在上面建立完 ScriptableNPObject 後,等於是建立了一個相對應的 JavaScript Object 。於是 JavaScript 可以對 JavaScriptObject 做其它的存取動作,而這些動作則被對應到 NPObject 的兩類 MarshalingFunctions:
(提醒一下:NPObject->_class就是 NPClass )
1. Method 呼叫
typedef bool(*NPHasMethodFunctionPtr)(NPObject *obj, NPIdentifier name); typedef bool(*NPInvokeFunctionPtr)(NPObject *obj, NPIdentifier name, const NPVariant *args,uint32_t argCount, NPVariant *result); typedef bool(*NPInvokeDefaultFunctionPtr)(NPObject *npobj, const NPVariant *args, uint32_targCount, NPVariant *result); struct NPClass { ... NPHasMethodFunctionPtr hasMethod; NPInvokeFunctionPtrinvoke; NPInvokeDefaultFunctionPtr invokeDefault; ... };
2. Property 存取
typedef bool(*NPHasPropertyFunctionPtr)(NPObject *obj, NPIdentifier name); typedef bool(*NPGetPropertyFunctionPtr)(NPObject *obj, NPIdentifier name, NPVariant*result); typedef bool (*NPSetPropertyFunctionPtr)(NPObject *obj, NPIdentifiername, const NPVariant *value); typedef bool(*NPRemovePropertyFunctionPtr)(NPObject *npobj, NPIdentifier name); structNPClass { ... NPHasPropertyFunctionPtr hasProperty; NPGetPropertyFunctionPtr getProperty; NPSetPropertyFunctionPtrsetProperty; NPRemovePropertyFunctionPtrremoveProperty; ... };
從以下範例說明會比較清楚:
<script>var myPlugin = document.getElementByIdx_x_x_x_x("FooPlugin");myPlugin.fooMethod(); myPlugin.fooProperty = "hello world";</script>
從 ECMAScript 的角度說明如下:
· myPlugin : "myPlugin" 是一個 JavaScriptObject 的名字(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 的值更改為 "helloworld" 的動作。
以上說明著動在流程上,細節上並非完全正確,因為 JavaScript(ECMAScript) 內部有許多針對 Objects,Properties, Attributes 等細節,可以說上三天三夜了吧!
NPVariant(Parameters Serialization between JavaScript and C)
在 MarshalingFunctions 中,會以 NPVariant 來傳送真正的參數資料。
NPVariant 就是參數的 Serialized DataType 。
typedef struct_NPVariant { NPVariantType type; union { bool boolValue; int32_t intValue;double doubleValue; NPString stringValue; NPObject *objectValue; } value; }NPVariant; typedef enum { NPVariantType_Void, NPVariantType_Null,NPVariantType_Bool, NPVariantType_Int32, NPVariantType_Double,NPVariantType_String, NPVariantType_Object } NPVariantType;
Data Type MappingBetween JavaScript and NPVariant
NPVariant 所封裝的資料型態會對應到 JavaScript 資料型態。
對應如下:
JavaScript | C (NPVariant with type:) |
undefined | NPVariantType_Void |
null | NPVariantType_Null |
Boolean | NPVariantType_Bool |
Number | NPVariantType_Double or NPVariantType_Int32 |
String | NPVariantType_String |
Object | NPVariantType_Object |
Marshaling Macro
為了方便 NPVariant 與 JavaScript 間的資料轉換, NPRuntime 也定義了一組轉換的 Macro 方便程式設計。
#defineNPVARIANT_IS_VOID(_v) ((_v).type == NPVariantType_Void) #defineNPVARIANT_IS_NULL(_v) ((_v).type == NPVariantType_Null) #defineNPVARIANT_IS_BOOLEAN(_v) ((_v).type == NPVariantType_Bool) #defineNPVARIANT_IS_INT32(_v) ((_v).type == NPVariantType_Int32) #defineNPVARIANT_IS_DOUBLE(_v) ((_v).type == NPVariantType_Double) #defineNPVARIANT_IS_STRING(_v) ((_v).type == NPVariantType_String) #defineNPVARIANT_IS_OBJECT(_v) ((_v).type == NPVariantType_Object) #defineNPVARIANT_TO_BOOLEAN(_v) ((_v).value.boolValue) #define NPVARIANT_TO_INT32(_v)((_v).value.intValue) #define NPVARIANT_TO_DOUBLE(_v) ((_v).value.doubleValue)#define NPVARIANT_TO_STRING(_v) ((_v).value.stringValue) #defineNPVARIANT_TO_OBJECT(_v) ((_v).value.objectValue) #define NP_BEGIN_MACRO do {#define NP_END_MACRO } while (0) #define VOID_TO_NPVARIANT(_v) NP_BEGIN_MACRO(_v).type = NPVariantType_Void; (_v).value.objectValue = NULL; NP_END_MACRO#define NULL_TO_NPVARIANT(_v) NP_BEGIN_MACRO (_v).type = NPVariantType_Null;(_v).value.objectValue = NULL; NP_END_MACRO #define BOOLEAN_TO_NPVARIANT(_val,_v) NP_BEGIN_MACRO (_v).type = NPVariantType_Bool; (_v).value.boolValue =!!(_val); NP_END_MACRO #define INT32_TO_NPVARIANT(_val, _v) NP_BEGIN_MACRO(_v).type = NPVariantType_Int32; (_v).value.intValue = _val; NP_END_MACRO#define DOUBLE_TO_NPVARIANT(_val, _v) NP_BEGIN_MACRO (_v).type =NPVariantType_Double; (_v).value.doubleValue = _val; NP_END_MACRO #defineSTRINGZ_TO_NPVARIANT(_val, _v) NP_BEGIN_MACRO (_v).type = NPVariantType_String;NPString str = { _val, strlen(_val) }; (_v).value.stringValue = str;NP_END_MACRO #define STRINGN_TO_NPVARIANT(_val, _len, _v) NP_BEGIN_MACRO(_v).type = NPVariantType_String; NPString str = { _val, _len };(_v).value.stringValue = str; NP_END_MACRO #define OBJECT_TO_NPVARIANT(_val,_v) NP_BEGIN_MACRO (_v).type = NPVariantType_Object; (_v).value.objectValue =_val; NP_END_MACRO
Why need NPVariant(Serialization) ?
在 JavaScript 中,使用者可以任意撰寫任何 function,而這些 function 的參數個數,型態,排列順序等,都是任意的;我們不可能寫出一個 C function 來對應到所有的 JavaScriptfunction,C function 是 compile time 時就必須決定參數個數,型態與順序;因此必須要透過 Marshaling(Serialization) 的方式來取得 JavaScript 的參數後,再轉換成 C 語言中相對應的資料型態來處理。
NPIdentifier
NPObject 的 Method 與 Property 的皆是由 NPIdentifier 來指定,NPIdentifier 對於相同名稱的 Method 或是 Property 會有一個 Unique 值。而 NPIdentifier 的值是由 Browser 所提供,也就是說 Browser 內部有一個 (Hash) Table 來儲存所有的 NPIdentifier 。
typedef void*NPIdentifier; NPIdentifier NPN_GetStringIdentifier(const NPUTF8 *name); voidNPN_GetStringIdentifiers(const NPUTF8 **names, int32_t nameCount, NPIdentifier*identifiers); NPIdentifier NPN_GetIntIdentifier(int32_t intid); boolNPN_IdentifierIsString(NPIdentifier identifier); NPUTF8*NPN_UTF8FromIdentifier(NPIdentifier identifier); int32_tNPN_IntFromIdentifier(NPIdentifier identifier);
Browser 另外還提供了 NPIdentifier 的轉換函數,供 Plugin 方便使用。
Why useNPIdentifier?
有 NPIdentifier 這樣的設計主要有兩個原因:
1. Less Memory Cost
對於許多 Object 來說都有相同名稱的 Method 或是 Property,若是將這些「名稱字串」全都儲存在 Object 的 Instance 中,對於 Memory 的消耗實在是一種浪費。
2. Fast Lookup (ECMAScript IdentiferResolution)
對於 Browser 或是 Plugin 在 JavaScript 執行時在 Lookup Object 的動作時,能夠以 LookupNPIdentifier 來取代 Name String Compare,可以大大增加 Lookup 的速度。