轉http://blog.csdn.net/zssureqh/article/details/9454899
本篇文章在探討 NPAPI與 NPRuntime的設計,並非 Plugin教學。
當時因爲看到公司內部寫出來的 Plugin問題不少,而且網絡上說明太少,特地寫來給大家看的~
故本篇沒有詳細介紹每個 API的使用與功能,請見諒囉!
This article was written in2009/04/08.
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
上面的 Sequence Diagram說明了 Browser與 Plugin 之間的運作過程:
1. Browser lookup Plugin (.so, .dll) and load it.
2. Browser呼叫 Plugin 的 NP_Initialize() 來交換彼此所需的 API FunctionPointers。
o 將 Browser Side的 NPN_API function table(NPNetscapeFuncs *aNPNFuncs)傳給 Plugin(Binding)。
o 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 則會呼叫 PluginNPP_Destroy()來通知 Plugin 應 Destroy 所對應的 Plugin instance。
6. 當 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*)
NPError OSCALL 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_DestroyStreamProcPtrdestroystream;
NPN_StatusProcPtr status;
NPN_UserAgentProcPtr uagent;
NPN_MemAllocProcPtr memalloc;
NPN_MemFreeProcPtr memfree;
NPN_MemFlushProcPtr memflush;
NPN_ReloadPluginsProcPtrreloadplugins;
NPN_GetJavaEnvProcPtr getJavaEnv;
NPN_GetJavaPeerProcPtr getJavaPeer;
NPN_GetURLNotifyProcPtr geturlnotify;
NPN_PostURLNotifyProcPtrposturlnotify;
NPN_GetValueProcPtr getvalue;
NPN_SetValueProcPtr setvalue;
NPN_InvalidateRectProcPtrinvalidaterect;
NPN_InvalidateRegionProcPtrinvalidateregion;
NPN_ForceRedrawProcPtr forceredraw;
NPN_GetStringIdentifierProcPtrgetstringidentifier;
NPN_GetStringIdentifiersProcPtrgetstringidentifiers;
NPN_GetIntIdentifierProcPtrgetintidentifier;
NPN_IdentifierIsStringProcPtridentifierisstring;
NPN_UTF8FromIdentifierProcPtr utf8fromidentifier;
NPN_IntFromIdentifierProcPtrintfromidentifier;
NPN_CreateObjectProcPtr createobject;
NPN_RetainObjectProcPtr retainobject;
NPN_ReleaseObjectProcPtrreleaseobject;
NPN_InvokeProcPtr invoke;
NPN_InvokeDefaultProcPtr invokeDefault;
NPN_EvaluateProcPtr evaluate;
NPN_GetPropertyProcPtr getproperty;
NPN_SetPropertyProcPtr setproperty;
NPN_RemovePropertyProcPtrremoveproperty;
NPN_HasPropertyProcPtr hasproperty;
NPN_HasMethodProcPtr hasmethod;
NPN_ReleaseVariantValueProcPtrreleasevariantvalue;
NPN_SetExceptionProcPtr setexception;
NPN_PushPopupsEnabledStateProcPtrpushpopupsenabledstate;
NPN_PopPopupsEnabledStateProcPtrpoppopupsenabledstate;
NPN_EnumerateProcPtr enumerate;
NPN_PluginThreadAsyncCallProcPtrpluginthreadasynccall;
NPN_ConstructProcPtr construct;
NPN_ScheduleTimerProcPtrscheduletimer;
NPN_UnscheduleTimerProcPtrunscheduletimer;
NPN_PopUpContextMenuProcPtrpopupcontextmenu;
} 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_DestroyStreamProcPtrdestroystream;
NPP_StreamAsFileProcPtr asfile;
NPP_WriteReadyProcPtr writeready;
NPP_WriteProcPtr write;
NPP_PrintProcPtr print;
NPP_HandleEventProcPtr event;
NPP_URLNotifyProcPtr urlnotify;
JRIGlobalRef javaClass;
NPP_GetValueProcPtr getvalue;
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,
uint16 mode,
int16 argc,
char* argn[],
char* argv[],
NPSavedData* saved);
NPError NPP_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
宣告如下:
/*
* NPP is a plug-in's opaque instance handle
*/
typedef struct _NPP
{
void* pdata; /* plug-in private data */
void* ndata; /* netscape private data */
} NPP_t;
typedef NPP_t* NPP;
而 Browser在某個 Page 被 Unload 之前,則會呼叫 NPP_Destroy()來通知 Plugin 結束所對應的 Plugin Instance。
Scriptability
Scriptability 就是讓 JavaScript可以將 Plugin 當作 JavaScriptObject來使用,而 NPRuntime定義了 NPObject與 NPClass 兩個結構來建立 Browser 能夠了解的 Scriptable Object。
MultipleNPObject Instances
該注意的一點是,NPObject本身也是需要支持 MultipleInstance,原因很簡單,因爲 Plugin Instance都應該擁有自己的 NPObject,若是 NPObject不設計成 Multiple Instance,就得所有 Plugin Instance「共享」一組 NPObject,將會帶來很多擴充性上的困難。
ScriptableObject 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其實是一個 ScriptableObject。
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需要建立 NPObjectInstance時,會呼叫 NPObject→NPClass→allocate(),也就會呼叫到 PluginClass→pluginAllocate(),我們就可以 newPluginObject()傳回給 Browser了。簡單的說,Browser想要建立或是存取任何 PluginObject,都得透過 PluginClass中的 API,Browser是無法直接存取 PluginObject的 CustomProperty/Methods。
雖然 NPRuntime的呼叫都是 C API,但是實際上這組 API 想完成的事就是上面的 Scriptable Object Model。若只是瞭解 Call Flow是不夠的,要能「讀出」原來設計這組 API 的人在「想」的是什麼。
NPClass 與 NPObject的 C 宣告如下:
struct NPClass
{
uint32_t structVersion;
NPAllocateFunctionPtr allocate;
NPDeallocateFunctionPtr deallocate;
NPInvalidateFunctionPtr invalidate;
NPHasMethodFunctionPtr hasMethod;
NPInvokeFunctionPtr invoke;
NPInvokeDefaultFunctionPtrinvokeDefault;
NPHasPropertyFunctionPtr hasProperty;
NPGetPropertyFunctionPtr getProperty;
NPSetPropertyFunctionPtr setProperty;
NPRemovePropertyFunctionPtrremoveProperty;
NPEnumerationFunctionPtr enumerate;
};
struct NPObject {
NPClass *_class;
uint32_t referenceCount;
// Additional space may be allocatedhere by types of NPObjects
}
When shouldNPObject 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 的範例:
#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 functions appeared inNPAPI 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()來 Allocate NPObject。傳回的 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-Oriented Language (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 即可。
範立如下:
NPError NPP_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;
return NPERR_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)
在上面建立完 Scriptable NPObject後,等於是建立了一個相對應的 JavaScript Object。於是 JavaScript可以對 JavaScript Object做其它的存取動作,而這些動作則被對應到 NPObject的兩類 Marshaling Functions:
(提醒一下:NPObject->_class就是 NPClass )
1. Method呼叫
typedef bool(*NPHasMethodFunctionPtr)(NPObject *obj, NPIdentifier name);
typedef bool (*NPInvokeFunctionPtr)(NPObject *obj, NPIdentifier name, constNPVariant *args, uint32_t argCount, NPVariant *result);
typedef bool (*NPInvokeDefaultFunctionPtr)(NPObject *npobj, const NPVariant*args, uint32_t argCount, NPVariant *result);
struct NPClass
{
...
NPHasMethodFunctionPtr hasMethod;
NPInvokeFunctionPtr invoke;
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, NPIdentifier name,const NPVariant *value);
typedef bool (*NPRemovePropertyFunctionPtr)(NPObject *npobj, NPIdentifier name);
struct NPClass
{
...
NPHasPropertyFunctionPtr hasProperty;
NPGetPropertyFunctionPtr getProperty;
NPSetPropertyFunctionPtr setProperty;
NPRemovePropertyFunctionPtr removeProperty;
...
};
從以下範例說明會比較清楚:
<script>
var myPlugin = document.getElementByID("FooPlugin");
myPlugin.fooMethod();
myPlugin.fooProperty = "hello world";
< /script>
從 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 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 TypeMapping Between 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方便程序設計。
#define NPVARIANT_IS_VOID(_v) ((_v).type == NPVariantType_Void)
#define NPVARIANT_IS_NULL(_v) ((_v).type == NPVariantType_Null)
#define NPVARIANT_IS_BOOLEAN(_v) ((_v).type == NPVariantType_Bool)
#define NPVARIANT_IS_INT32(_v) ((_v).type == NPVariantType_Int32)
#define NPVARIANT_IS_DOUBLE(_v) ((_v).type == NPVariantType_Double)
#define NPVARIANT_IS_STRING(_v) ((_v).type == NPVariantType_String)
#define NPVARIANT_IS_OBJECT(_v) ((_v).type== NPVariantType_Object)
#define NPVARIANT_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)
#define NPVARIANT_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
#define STRINGZ_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 needNPVariant (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。
typedef void *NPIdentifier;
/*
NPObjects have methods and properties. Methods and properties are
identified with NPIdentifiers. These identifiers may be reflected
in script. NPIdentifiers can be either strings orintegers, IOW,
methods and properties can beidentified by either strings or
integers (i.e. foo["bar"]vs foo[1]). NPIdentifiers can be
compared using ==. In case of any errors, the requested
NPIdentifier(s) will be NULL.
*/
NPIdentifier NPN_GetStringIdentifier(const NPUTF8 *name);
void NPN_GetStringIdentifiers(const NPUTF8 **names, int32_t nameCount,
NPIdentifier *identifiers);
NPIdentifier NPN_GetIntIdentifier(int32_t intid);
bool NPN_IdentifierIsString(NPIdentifier identifier);
/*
The NPUTF8 returned fromNPN_UTF8FromIdentifier SHOULD be freed.
*/
NPUTF8 *NPN_UTF8FromIdentifier(NPIdentifier identifier);
/*
Get the integer represented byidentifier. If identifier is not an
integer identifier, the behaviour isundefined.
*/
int32_t NPN_IntFromIdentifier(NPIdentifier identifier);
Browser 另外還提供了 NPIdentifier的轉換函數,供 Plugin方便使用。
Why useNPIdentifier?
有 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 StringCompare,可以大大增加 Lookup的速度。
Reference
· https://developer.mozilla.org/en/Plugins