NPAPI插件開發學習:NPAPI和NPRuntime的介紹

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時,會呼叫 NPObjectNPClassallocate(),也就會呼叫到 PluginClasspluginAllocate(),我們就可以 newPluginObject()傳回給 Browser了。簡單的說,Browser想要建立或是存取任何 PluginObject,都得透過 PluginClass中的 APIBrowser是無法直接存取 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來說,instancepdata所存放的其實是 PluginObject;之後 Browser呼叫 PluginClass中的 Methods 時,我們只需要取得 instancepdata就可以當做是 PluginObject,並直接存由 Plugin自己定義的 CustomProperties/Methods了。如此一來,就不用一堆 Variable指來指去的了,這是簡化支持 MultipleInstance的一個重點。

這樣的技巧在 AppleObjective-C Object-Oriented Language (ex: C++vtable)裏是很常見的,可惜的是我們目前的實作完全沒有 OO的思考。
instance->pdata
反正是個 void *,可以任意 casting成任一種 typeBrowser 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 functionC 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

 


發佈了7 篇原創文章 · 獲贊 6 · 訪問量 17萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章