firefox的插件分兩種類型,一種extension,叫擴展,一種是plugin,我們叫插件.兩種是完全不同的兩個東西。extension相對來說簡單很多,用的主要是XUL,只是xml的一個變相。而plugin相對來說復雜一些。具體的官網說明如下https://developer.mozilla.org/en/Gecko_Plugin_API_Reference
一、 插件的標准
1、在windows平台,火狐的插件是以動態庫形式(dll)存在的,並只去識別在其安裝目錄下plugins文件夾下的dll;
2、dll的名是以8.3原則來命名的,即其名字所包含的字符不超過8個字符且以dll結尾的文件,還有一個規定就是其名字必須以np開頭,例如:npXXX.dll,而XXX不超過六個字符;
3、dll是屬於MIME(一種標准)類型,要不火狐瀏覽器不認識他;
4、dll導出的函數必須是NP_GetEntryPoints、NP_Initialize、NP_Shutdown是這三個,這是火狐瀏覽器能夠識別的在個接口,具體說明在插件的生命周期中說明。
二、 插件的生命周期
1、 第一次打開含量有插件的頁面時,瀏覽器最先調用NP_GetEntryPoints作為調用插件的入口,此方法也只在第一次加載插件時調用。
2、 調用NP_GetEntryPoints后,瀏覽器會調用NP_Initialize初始化插件,NP_Initialize只在第一次調用時被瀏覽器調 用,與NP_Initialize配對的是NP_Shutdown,NP_Shutdown是在關閉了所有含有該插件的頁面后被瀏覽器調用,在生命周期內 也只被調用一次
3、 調用NP_Initialize后,瀏覽器會調會NPP_New來創建一個插件實例,每打開一個頁面都會調用NPP_New一次來創建一個插件實例,與 NPP_New配對的是NPP_Destory,在每關閉一個頁面都會調用NPP_Destory來釋放NPP_New創建的實例
4、 調用NP_Initialize后,一般會調用NPP_SetWindow來調置窗口,對於沒有窗口的插件當然不用調用。
注:火狐插件開發的sdk划分為兩類接口,一類是與插件相關的,以NPP或者NP開頭的;一類是與瀏覽器相關的,以NPN開頭的。
三、 頁面調用插件的方式
1、 通過object方式調用:與IE調用ActiveX控件的方式一樣的,不同的是IE調用ActiveX控件是通過ClassID來標識的,而火狐的控件是通過MIME的值來標識。
2、 通過embed方式調用:與object方式類似,但操作方面簡單一些
四、 插件與頁面的信息傳遞
插件與頁面的信息是通過插件的接口與JS進行信息傳遞。在火狐插件有一類接口可以直接讀取頁面的JS函數,並從函數中獲取相應的值或者調置相關的值。
具體的例子有時間再加上,現在還在頭大中。。。。
三、火狐調用插件的過程
NP_GetEntryPoints → NP_Initialize → NPP_New → NPP_SetWindow → NPP_GetValue
在NPP_New中,我們需要創建插件對象的實例,NPP_SetWindow中,瀏覽器會傳入插件窗口的信息,最后一個NPP_GetValue,是瀏覽器來獲取一些插件信息的。
NPP_GetValue函數的結構:
NPError NPP_GetValue(NPP instance, NPPVariable variable, void *value);
- instance包含着插件對象實例;
- variable表示瀏覽器要獲取的信息的類型;
- value表示返回給瀏覽器的值
瀏覽器會傳入NPPVpluginScriptableNPObject(作為variable參數)來查詢插件是否支 持 Scriptable功能(即和腳本語言交互的功能),在這里,我們可以利用NPN_CreateObject方法來創建一個NPObject對象,並且 作為value返回給瀏覽器。這樣,瀏覽器就通過這個NPObject對象和我們的插件建立了連接。當頁面上Javascript調用了我們插件對象的某 個方法時,瀏覽器會調用該NPObject對象的HasMethod方法來查詢是否支持這個方法,如果支持,則會調用NPObject對象的Invoke 方法,傳入方法名、參數等信息。這樣,我們就可以讓網頁上的腳本語言來執行我們編寫的函數了。在Windows上,我們編寫的函數就如同編寫普通的應用程 序一樣,可以使用很多Windows API來完成許多復雜的工作。
上面有個問題:如何創建我們自己的NPObject對象?NPN_CreateObject方法如何使用?好在Mozilla給我們提供了npruntime這個例子程序,可以讓我們得以參考。
先來看看NPN_CreateObject方法的定義:
NPObject *NPN_CreateObject(NPP npp, NPClass *aClass);
關鍵在第二個參數上,我們需要提供一個NPClass指針。npruntime例子程序中是這么做的:
定 義了一個宏DECLARE_NPOBJECT_CLASS_WITH_BASE,其作用就是定義了一個靜態的NPClass對象,並且 NPClass要求的所有基礎方法,都由一個ScriptablePluginObjectBase類來提供。我們根據需要,來創建不同的繼承於 ScriptablePluginObjectBase的類(比如支持方法的類和支持屬性的類),傳給 DECLARE_NPOBJECT_CLASS_WITH_BASE宏,這樣,當瀏覽器管我們“要”的時候,我們就可以按照它的需要“給”它對應的對象。
npruntime例子中,ScriptablePluginObject是用來處理方法的,而ConstructablePluginObject是用來處理屬性的。
如何定義一個方法(或屬性)?
1、添加一個方法(或屬性)很簡單,先定義一個靜態NPIdentifier類型的變量,例如:
static NPIdentifier s_idSetArgs;
2、在插件對象構造函數中,使用NPN_GetStringIdentifier方法來設置該方法的名稱,例如:
s_idSetArgs = NPN_GetStringIdentifier("SetArgs");
其中,SetArgs就是我們提供給腳本語言調用的方法名稱。
3、在ScriptablePluginObject的HasMethod方法中,判斷傳入的方法名:
bool ScriptablePluginObject::HasMethod(NPIdentifier name)
{
if(name == s_idSetArgs)
{
printf("method name = SetArgs\n");
return true;
}
return false;
}
4、在ScriptablePluginObject的Invoke方法中,判斷如果傳入的方法名稱等於我們定義的方法名,則做你想要做得事情:
//////////////////////////////////////////////////////////////////////////
///
/// @brief 如果某個方法支持(使用HasMethod檢測),當頁面上Javascript代碼調用該方法時,會執行本函數
///
/// @param [in] name 方法名
/// @param [in] args 參數值(數組)
/// @param [in] argCount 參數個數
/// @param [in] result 執行后返回給調用者的結果
///
/// @return PR_TRUE表示執行成功,PR_FALSE表示失敗
///
//////////////////////////////////////////////////////////////////////////
bool ScriptablePluginObject::Invoke(NPIdentifier name, const NPVariant *args, uint32_t argCount, NPVariant *result)
{
if(name == s_idSetArgs)
{
這里做你想要做得事情
return PR_TRUE;
}
return PR_FALSE;
}
關於方法參數的接收,這里舉個例子。比如網頁上這么調用:
embedobj.SetArgs("name", "value");
在我們的方法中,就可以這么接收:
if(args != NULL && argCount >= 2)
{
NPVariant npvName = args[0]; //第一個參數
NPVariant npvValue = args[1]; //第二個參數
if(NPVARIANT_IS_STRING(npvName) && NPVARIANT_IS_STRING(npvValue)) //如果兩者都是字符串類型(當然你還可以判斷是否是其他類型)
{
NPString npsName = NPVARIANT_TO_STRING(npvName); //轉成NPString
NPString npsValue = NPVARIANT_TO_STRING(npvValue);
if(npsName.utf8characters && strlen(npsName.utf8characters) > 0) //限定條件,可以根據需要進行修改。這里限定第一個參數內容不能為空
{
int nLenName = strlen(npsName.utf8characters) + 1;
int nLenValue = strlen(npsValue.utf8characters) + 1;
PARAMPAIR paramPair;
paramPair.pName = new char[nLenName];
memset(paramPair.pName, 0, nLenName);
paramPair.pValue = new char[nLenValue];
memset(paramPair.pValue, 0, nLenValue);
strcpy(paramPair.pName, npsName.utf8characters); //將參數內存存儲到我們熟悉的C
strcpy(paramPair.pValue, npsValue.utf8characters);
m_vecParamPair.push_back(paramPair);
}
}
}
上面的代碼中,PARAMPAIR就是一個簡單的結構體:
typedef struct tagPARAMPAIR
{
LPTSTR pName;
LPTSTR pValue;
}PARAMPAIR, *PPARAMPAIR;
m_vecParamPair是一個vector:vector<PARAMPAIR> m_vecParamPair;
順便說一句,上面只是代碼片段,關於內存釋放、vector清空等操作,由於不是這里要說的關鍵部分,所以沒有列出。
OK,現在我們的插件已經可以順利和網頁進行交互工作了。