1. 程式人生 > >NPAPI外掛開發詳細記要:外掛執行流程分析

NPAPI外掛開發詳細記要:外掛執行流程分析

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