本篇文章在探討 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的速度。