本篇文章在探讨 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
· The NPAPI Plugin Guide