NPAPI插件開發詳細記錄:插件運行流程分析

本文詳細分析插件的代碼是如何執行的,主要分析np_entry.cpp、npn_gate.cpp和npp_gate.cpp.希望能夠有所收穫。
在windows平臺下,插件就是一個dll,注意到這個dll的def文件內容是:

LIBRARY ""

EXPORTS
NP_GetEntryPoints @1
NP_Initialize @2
NP_Shutdown @3

插件接口

既然是瀏覽器調用插件,必然瀏覽器是通過上面三個接口來調用的。上述三個接口,第三個很明顯是結束插件時調用。
參考資料:http://colonelpanic.net/2009/03/building-a-firefox-plugin-part-one/
NP_GetEntryPoints – 在插件加載之後立即調用該接口,用於瀏覽器獲取所有可能需要調用的API函數的指針。
NP_Initialize – 爲插件提供全局初始化。
NP_Shutdown – 爲插件提供全局反初始化。
在np_entry.cpp文件中可以找到上面的幾個函數。第一個:

NPError OSCALL NP_GetEntryPoints(NPPluginFuncs* aNPPFuncs)
{
return fillPluginFunctionTable(aNPPFuncs);
}
其調用的fillPluginFunctionTable爲:
static NPError fillPluginFunctionTable(NPPluginFuncs* aNPPFuncs)
{
if (!aNPPFuncs)
return NPERR_INVALID_FUNCTABLE_ERROR;

// Set up the plugin function table that Netscape will use to call us.
aNPPFuncs->version = (NP_VERSION_MAJOR << 8) | NP_VERSION_MINOR;
aNPPFuncs->newp = NPP_New;
aNPPFuncs->destroy = NPP_Destroy;
aNPPFuncs->setwindow = NPP_SetWindow;
aNPPFuncs->newstream = NPP_NewStream;
aNPPFuncs->destroystream = NPP_DestroyStream;
aNPPFuncs->asfile = NPP_StreamAsFile;
aNPPFuncs->writeready = NPP_WriteReady;
aNPPFuncs->write = NPP_Write;
aNPPFuncs->print = NPP_Print;
aNPPFuncs->event = NPP_HandleEvent;
aNPPFuncs->urlnotify = NPP_URLNotify;
aNPPFuncs->getvalue = NPP_GetValue;
aNPPFuncs->setvalue = NPP_SetValue;

return NPERR_NO_ERROR;
}
fillPluginFunctionTable函數設置了一系列的函數入口,都是很熟悉的在mozilla的開發文檔中經常提到的以NPP開頭的函數。這些函數是需要在插件中加以實現的。
第二個:

NPError OSCALL NP_Initialize(NPNetscapeFuncs* aNPNFuncs)
{
NPError rv = fillNetscapeFunctionTable(aNPNFuncs);
if (rv != NPERR_NO_ERROR)
return rv;

return NS_PluginInitialize();
}
其調用的fillNetscapeFunctionTable爲:
static NPError fillNetscapeFunctionTable(NPNetscapeFuncs* aNPNFuncs)
{
if (!aNPNFuncs)
return NPERR_INVALID_FUNCTABLE_ERROR;

if (HIBYTE(aNPNFuncs->version) > NP_VERSION_MAJOR)
return NPERR_INCOMPATIBLE_VERSION_ERROR;

if (aNPNFuncs->size < sizeof(NPNetscapeFuncs))
return NPERR_INVALID_FUNCTABLE_ERROR;

NPNFuncs.size = aNPNFuncs->size;
NPNFuncs.version = aNPNFuncs->version;
NPNFuncs.geturlnotify = aNPNFuncs->geturlnotify;
NPNFuncs.geturl = aNPNFuncs->geturl;
NPNFuncs.posturlnotify = aNPNFuncs->posturlnotify;
NPNFuncs.posturl = aNPNFuncs->posturl;
NPNFuncs.requestread = aNPNFuncs->requestread;
NPNFuncs.newstream = aNPNFuncs->newstream;
NPNFuncs.write = aNPNFuncs->write;
NPNFuncs.destroystream = aNPNFuncs->destroystream;
NPNFuncs.status = aNPNFuncs->status;
NPNFuncs.uagent = aNPNFuncs->uagent;
NPNFuncs.memalloc = aNPNFuncs->memalloc;
NPNFuncs.memfree = aNPNFuncs->memfree;
NPNFuncs.memflush = aNPNFuncs->memflush;
NPNFuncs.reloadplugins = aNPNFuncs->reloadplugins;
NPNFuncs.getvalue = aNPNFuncs->getvalue;
NPNFuncs.setvalue = aNPNFuncs->setvalue;
NPNFuncs.invalidaterect = aNPNFuncs->invalidaterect;
NPNFuncs.invalidateregion = aNPNFuncs->invalidateregion;
NPNFuncs.forceredraw = aNPNFuncs->forceredraw;

return NPERR_NO_ERROR;
}
這裏獲取一系列函數的入口,這些函數是瀏覽器中實現的。
NP_Initialize還調用了一個函數NS_PluginInitialize(),NS_PluginInitialize是我們在plugin.cpp中實現的,隨便提一句,NP_Shutdown調用的NS_PluginShutdown也是在plugin.cpp中由我們自己去實現的。
通過對上面的代碼的分析,可以發現雖然在def裏面只定義了三個接口,但實際上卻包括瀏覽器實現的由插件調用的接口21個以及瀏覽器要調用的由插件實現的接口13個(實際上NPNetscapeFuncs結構定義了55個函數指針,NPPluginFuncs結構定義了19個函數指針)。換句話說,我們開發插件就是要來實現這13個接口。接口的標準已經由瀏覽器定義好了,我們怎麼去實現以及要實現什麼樣的功能就全憑我們自己了。
np_entry.cpp這個文件已經分析得差不多了,這個文件包含了兩個頭文件,"npplat.h"和"pluginbase.h",打開這兩個文件來看看裏面定義了些什麼。npplat.h很簡單:
#ifndef npplat_h_
#define npplat_h_

#include "npapi.h"
#include "npfunctions.h"

#ifdef XP_WIN
#include "windows.h"
#endif

#ifdef XP_UNIX
#include <stdio.h>
#endif

#ifdef XP_MAC
#include <Carbon/Carbon.h>
#endif

#ifndef HIBYTE
#define HIBYTE(i) (i >> 8)
#endif

#ifndef LOBYTE
#define LOBYTE(i) (i & 0xff)
#endif

#endif
另外看看pluginbase.h,該文件定義了nsPluginInstanceBase類並聲明瞭四個全局函數: NS_NewPluginInstance(nsPluginCreateData * aCreateDataStruct)
NS_DestroyPluginInstance(nsPluginInstanceBase * aPlugin);
NS_PluginInitialize();
NS_PluginShutdown();
這四個函數都是由我們在plugin.cpp中去實現的,前面的分析中已經知道NS_PluginInitialize()和NS_PluginShutdown()是在np_entry.cpp文件中調用的。
由此觀之,我們要開發插件,建立的類首先要繼承nsPluginInstanceBase類根據需要實現其中的某些虛函數,另外還需要實現上面這四個全局函數。
接下來研究npn_gate.cpp文件和npp_gate.cpp文件。

NPN函數

打開npn_gate.cpp文件,就可以發現其中實現了20個函數,都是fillNetscapeFunctionTable中的函數,(之前我們發現其中有21個函數,這裏爲什麼只有20個呢?看看NPNetscapeFuncs的定義可以發現,size是個變量不是函數。)而這裏的函數名就是以NPN_開頭了,按說這是瀏覽器實現函數不需要在這些地方來實現了呀,這是什麼原因呢。來看看其中的函數吧。
以一個簡單的函數NPN_GetURL爲例:其實現代碼如下:

NPError NPN_GetURL(NPP instance, const char *url, const char *target)
{
return (*NPNFuncs.geturl)(instance, url, target);
}
這個一看咱就明白了,NPN_GetURL確實可以說是瀏覽器實現的,因爲這個函數直接調用了NPNetscapeFuncs的一個函數地址處的函數。
換句話說,在插件實例初始化的時候,將瀏覽器實現的這些函數的入口地址保存到一個NPNetscapeFuncs結構中,NPN_開頭的這些函數名其實是開發插件時需要由開發者定義的,這些函數的實現就直接根據NPNetscapeFuncs結構中的入口地址調用瀏覽器實現的相關功能。但這些基本都是固定不變的,因此sdk中已經幫我們開發者寫好了這些代碼,在開發插件時只需要調用NPN_開頭的全局函數即可。

NPP函數

npp_gate.cpp文件中實現了13個函數,這13個函數就是NP_GetEntryPoints中fillPluginFunctionTable需要實現的函數。
在npp_gate.cpp文件中我們可以發現分別在NPP_New和NPP_Destroy中調用了plugin.cpp中實現的另外兩個全局函數NS_NewPluginInstance和NS_DestroyPluginInstance。
這個文件中實現的13個函數基本上都調用了nsPluginInstanceBase類對應的函數,因此這些NPP_開頭的函數就相當於是插件開發者實現的。實現這些函數就是要實現nsPluginInstanceBase類的成員函數,可以看到nsPluginInstanceBase的成員函數都是定義爲虛函數的,其中 init(NPWindow* aWindow) 、shut()、 isInitialized()三個函數是純虛函數,在nsPluginInstanceBase類的派生類中必須進行實現,而其他函數就可以根據需要加以實現。
通過上面的一番分析,要寫出一個NPAPI的插件,利用這個框架必須至少要做的工作有:
從nsPluginInstanceBase類派生一個類並至少實現其中init() 、shut()、 isInitialized()這三個成員函數。
聲明並實現四個全局函數

nsPluginInstanceBase * NS_NewPluginInstance(nsPluginCreateData * aCreateDataStruct);
void NS_DestroyPluginInstance(nsPluginInstanceBase * aPlugin);
NPError NS_PluginInitialize();
void NS_PluginShutdown();
到這裏就對插件的接口分析完畢了,開發插件的過程就是實現nsPluginInstanceBase類的部分成員函數以實現需要的功能。要想得心應手的開發插件就必須準確的把握瀏覽器調用這些NPP函數的機制和流程。

瀏覽器對插件接口的調用

接下來分析瀏覽器在何時調用何種接口的問題。
首先來理一下nsPluginInstanceBase類的成員函數是如何被NPP_開頭的函數所調用的。(注:plugin表示一個nsPluginInstanceBase對象)

NPP接口函數

可能調用的nsPluginInstanceBase成員函數或全局函數

備註

NPP_New

NS_NewPluginInstance

創建插件實例

NPP_Destroy

NS_DestroyPluginInstance、plugin->shut

刪除插件實例

NPP_SetWindow

plugin->SetWindow、plugin->isInitialized、plugin->init、NS_DestroyPluginInstance

窗口創建、移動、改變大小或銷燬時調用

NPP_NewStream

plugin->NewStream

通知插件實例有新的數據流

NPP_WriteReady

plugin->WriteReady

確定插件是否準備好接收數據(以及其準備接收的最大字節數)

NPP_Write

plugin->Write

調用以將數據讀入插件this might be better named “NPP_DataArrived”

NPP_DestroyStream

plugin->DestroyStream

通知插件實例數據流將要關閉或銷燬

NPP_StreamAsFile

plugin->StreamAsFile

爲創建流數據提供本地文件名

NPP_Print

plugin->Print

爲嵌入或全屏插件請求平臺特定的打印操作

NPP_URLNotify

plugin->URLNotify

通知插件已完成URL請求

NPP_GetValue

plugin->GetValue

調用以查詢插件信息(還用來獲取NPObject/Scriptable 插件的實例)

NPP_SetValue

plugin->SetValue

這是用來爲瀏覽器提供插件變量信息的

NPP_HandleEvent

plugin->HandleEvent

事件處理函數,對windowed的插件只在MAC操作系統上可用,對於winless的插件所有平臺都可用

可見,NPP接口基本上與nsPluginInstanceBase類的成員函數一一對應。
下面這段文字譯自:http://colonelpanic.net/2009/05/building-a-firefox-plugin-part-two/

當你明確了插件的生命週期之後會發現它事實上非常簡單。初始化入口 NP_Initialize和NP_GetEntryPoints的調用順序不確定(根據相關文檔);但是在實際中,Windows平臺上貌似NP_GetEntryPoints 先被調用。記住這一點,下面就是Windows平臺上基本的windowed插件初始化的調用順序:
1. NP_GetEntryPoints – 插件用NPP_New, NPP_Destroy, NPP_SetWindow等函數的入口地址填充一個函數表。
2. NP_Initialize – 插件存儲一個NPN_CreateObject, NPN_MemAlloc,等函數入口地址組成的函數表的拷貝。
3. NPP_New – 插件創建一個新的插件實例並初始化
4. NPP_SetWindow – 每個實例都會多次調用這個函數——每次實例窗口創建、改變大小或者其他變化都會調用。
5. NPP_GetValue (Variable = NPPVpluginScriptableNPObject) – 插件創建一個支持腳本的NPObject並返回其指針(調用NPN_RetainObject)。
6. — 標準的插件活動 —
7. NPP_Destroy – 銷燬插件實例
8. NP_Shutdown – 銷燬所有遺留的插件資源



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章