1. 程式人生 > >NPAPI 外掛執行流程分析

NPAPI 外掛執行流程分析

本文詳細分析外掛的程式碼是如何執行的,主要分析np_entry.cpp、npn_gate.cpp和npp_gate.cpp.希望能夠有所收穫。

在windows平臺下,外掛就是一個dll,注意到這個dll的def檔案內容是:

NP_GetEntryPoints 
NP_Initialize
NP_Shutdown

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類的成員函式一一對應。

初始化入口 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 – 銷燬所有遺留的外掛資源