1.插件是什么
插件是一種遵循一定規范的應用程序接口編寫出來的程序。插件必須依附於一個宿主程序,為宿主程序提供增強功能。插件的種類有很多,這里主要討論瀏覽器插件。
IE下利用OLE和COM技術開發的瀏覽器插件稱為ActiveX控件。一般以.ocx為擴展名。IE瀏覽器通過OLE標准與ActiveX控件之間進行交互,完成對現有功能的擴充。
IE和OLE技術都是微軟出的,所以如果在非IE瀏覽器下開發插件,就不能使用現有的COM技術標准。NPAPI技術提供了一種新的開發瀏覽器下插件的標准。瀏覽器和插件共同遵守這一標准,完成功能和交互。
2.Netscape插件
支持NPAPI標准的瀏覽器有很多,這里以Firefox為例進行說明,不同的瀏覽器在具體的實現上可能有所不同。
Netscape插件是遵循NPAPI標准開發出的對瀏覽器擴展的插件模塊。 在firefox下,將插件(dll)放入firefox安裝目錄下的plugins文件夾中,啟動firefox,瀏覽器會通過插件的MimeType屬性識別出該插件。可以在地址欄中輸入about:plugins來查看插件是否被firefox正確識別,也可以通過設置注冊表的方式來安裝該插件,可以創建HKEY_LOCAL_MACHINE\Software\\MozillaPlugins\\@xxx.cn/xxxx,添加Path字符串項保存插件路徑。瀏覽器會自動加載。
3.MimeType
Mimetype,媒體資源類型,一般用於設定某種擴展名的文件用一種應用程序打開的方式。在編寫Netscape插件時,在資源文件中設置該插件的MimeType屬性值。如:
- BEGIN
- BLOCK "040904e4"
- BEGIN
- VALUE "CompanyName", "?????"
- VALUE "FileDescription", "TODO: <File description>"
- VALUE "FileVersion", "1.0.0.1"
- VALUE "InternalName", "npplugins.dll"
- VALUE "LegalCopyright", "TODO: (c) <Company name>. All rights reserved."
- VALUE "OriginalFilename", "npplugins.dll"
- VALUE "ProductName", "????"
- VALUE "ProductVersion", "1.0.0.1"
- VALUE "MIMEType", "application/test-plugins"
- END
- END
BEGIN
BLOCK "040904e4"
BEGIN
VALUE "CompanyName", "?????"
VALUE "FileDescription", "TODO: <File description>"
VALUE "FileVersion", "1.0.0.1"
VALUE "InternalName", "npplugins.dll"
VALUE "LegalCopyright", "TODO: (c) <Company name>. All rights reserved."
VALUE "OriginalFilename", "npplugins.dll"
VALUE "ProductName", "????"
VALUE "ProductVersion", "1.0.0.1"
VALUE "MIMEType", "application/test-plugins"
END
END
當瀏覽器啟動后,會在默認的plugins文件夾中加載插件,並讀取插件的MimeType屬性,並保存到瀏覽器內部。輸入about:plugins即可以獲取所有當前識別出的插件已經MimeType屬性。當瀏覽器加載頁面遇到<Embed ID = 'plugins1' type='application/test-plugins'/>這樣的文件內容時,即知道應該調用MimeType屬性值為type的插件來打開該文件。就會在瀏覽器中查找到該插件,並進行加載和初始化。如果不存在該屬性的插件,則因為找不到指定類型的插件而無法打開該媒體文件。
微軟的IE瀏覽器下的ActiveX控件遵循了COM標准,所以使用Object標簽的CLSID來標識應該調用那個插件來讀取該文件,不需要為插件設定MIME 的編碼。
4.Netscape插件的生命周期
1.打開firefox瀏覽器。Firefox主動讀取安裝目錄下的plugins文件夾,讀取該文件夾下的插件的信息,比如mimetype,並保存起來,輸入about:plugins可以查看
2.載入相關頁面。當遇到標簽為<Embed ID = 'plugins1' type='application/test-plugins'/>這樣的文件內容時,瀏覽器主動查找加載mimetype相匹配的插件。加載的過程也是初始化調用接口的過程。初始化完畢后,瀏覽器會調用插件相關接口創建一個插件實例,與該頁面相關聯。
3..載入另一個新的頁面。如果已經有頁面載入,則此后的所有重新打開的web頁面都將跳過插件初始化這一步驟,不過要創建新的插件實例與新的頁面相對應。
4..關閉一個頁面。銷毀與該頁面關聯的插件實例,如果是最后一個頁面,則執行反初始化插件操作。
5. NPAPI標准的接口說明
NPAPI標准定義在一組包含了數據結構和接口函數的頭文件中。在實際的開發中,需要將這些頭文件加入到工程中,並對頭文件定義的部分接口編寫實現。用到的主要四個頭文件有npapi.h,npfunction.h,npruntime.h,nptype.h,這四個頭文件可以在firefox的源碼例子插件中找到,同時還提供了一組C++編寫的框架用來更方便的開發Netscape插件。
簡單介紹一下三類接口函數
1..前綴NP_是npapi的插件庫提供給瀏覽器的最上層的接口,一般為動態鏈接庫的導出接口,主要有NP_GetEntryPoints、NP_Initialize、NP_GetMIMEDescription、 NP_GetValue、NP_Shutdown的等幾個函數,不同平台的接口可能略有不同,但基本功能都是一樣的,都是通過接口來初始化、銷毀以及認知此動態庫.
- LIBRARY "npplugins"
- EXPORTS
- ; Explicit exports can go here
- NP_GetEntryPoints @1
- NP_Initialize @2
- NP_Shutdown @3
LIBRARY "npplugins"
EXPORTS
; Explicit exports can go here
NP_GetEntryPoints @1
NP_Initialize @2
NP_Shutdown @3
2. 前綴NPP_即NP Plugin是插件本身提供給瀏覽器調用的接口,主要被用來填充NPPluginFuncs的結構體,主要包括:NPP_New、NPP_Destroy、NPP_SetWindow、NPP_GetMIMEDescription 、NPP_NewStream、NPP_DestroyStream、NPP_StreamAsFile、NPP_WriteReady、NPP_Write、NPP_Print、NPP_HandleEvent、NPP_URLNotify、NPP_GetValue、NPP_SetValue等,詳細介紹
NPP_Destroy 刪除插件的一個運行實例(instance) NPP_DestroyStream 告知插件將要刪除一個流數據 NPP_GetValue 供瀏覽器查詢插件的內部信息 NP_GetValue 供瀏覽器查詢插件的內部信息 NPP_HandleEvent 注冊關心的事件,當事件發生時瀏覽會通知插件 NP_Initialize 只調用一次,在插件加載時調用 NPP_New 創建插件的一個實例 NPP_NewStream 通知插件實例出現了新的流數據 NPP_Print 請求嵌入式打印還是全屏打印 NPP_SetValue 插件添加變量信息 NPP_SetWindow 當窗口創建、移動、改變大小或者銷毀時通知插件 NP_Shutdown 銷毀插件,與NP_Initialize對應 NPP_StreamAsFile 為數據流提供一個本地文件名 NPP_URLNotify 插件要求通知后,當對於某個URL的請求完成后,瀏覽器通知插件 NPP_Write 插件讀取流數據 NPP_WriteReady 調用NPP_Write之前調用,確實插件可以接收多少字節的數據
3.前綴NPN_XXX類接口一般為瀏覽器引擎提供給plugin調用的接口主要包括NPN_GetURL、NPN_PostURL、NPN_GetValue、NPN_SetValue、NPN_Status等
當瀏覽器開始加載插件時,首先調用NP_GetEntryPoint函數用於獲取NPP_類函數的地址,為后續調用做初始化工作。接着調用NP_Initialize函數,將瀏覽器提供的NPN_函數地址通知插件。以上兩個動態庫導出接口完成了插件后續調用和被調用的初始化工作。當所有插件相關頁面退出時,瀏覽器調用NP_Shutdown來完成反初始化。
接下來當firefox加載頁面時,瀏覽器調用插件的NPP_New函數來創建一個插件實例。當頁面退出時,瀏覽器調用NPP_Destroy來銷毀插件實例。注意,只有在第一次加載頁面時才會初始化調用插件,后續打開的頁面不再重新初始化,因為在第一次初始化時瀏覽器已經獲取了函數接口信息,后續的頁面只需要調用NPP_New創建新的插件實例與頁面對應即可。同理,只有當最后一個頁面退出時,才調用插件的反初始化,即NP_Shutdown函數。這時,瀏覽器會清除該插件的對象實例,釋放資源。
6.NPAPI的主要數據結構
1. 結構NPPluginFuncs:包含了NPP_函數地址的結構體。瀏覽器中定義一個這樣的結構體對象,通過NP_GetEntryPoint函數對該結構體對象賦值,將NPP_函數地址通知瀏覽器,供瀏覽器后續調用
2. 結構NPNetscapeFuncs:包含了瀏覽器中定義的NPN_函數的結構體。插件中定義一個這樣的結構體指針,瀏覽器通過NP_Initialize函數賦值給這個結構體指針,為后續插件調用瀏覽器中的這些接口服務。
3. 結構NPObject:包含NPClass *_class和uint32_t referenceCount. 插件實例對象
4. 結構NPClass: 包含了訪問插件的一組方法。包含在NPObject對象中。主要包含的函數有pluginHasMethod :詢問插件是否支持某一js方法。pluginHasProperty :詢問插件是否具有某一屬性 pluginInvoke :當插件支持該方法時,瀏覽器調用給方法傳遞參數和獲取返回值。
5. 結構NPVariant:帶類型定義的數據信息結構
定義為
typedef struct _NPVariant {
NPVariantType type;
union {
bool boolValue;
int32_t intValue;
double doubleValue;
NPString stringValue;
NPObject *objectValue;
} value;
} NPVariant;
對該結構體的訪問可以通過以下的標准宏進行:
NPVARIANT_IS_STRING或者NPVARIANT_TO_BOOLEAN,NPVARIANT_TO_INT32,NPVARIANT_TO_DOUBLE,NPVARIANT_TO_STRING,NPVARIANT_TO_OBJECT等。
NPAPI接口同時提供了一些將其他類型轉換為NPVariant結構的宏。如INT32_TO_NPVARIANT,STRINGZ_TO_NPVARIANT,OBJECT_TO_NPVARIANT等,可以很方便的進行數據轉換。
7.插件與JS的交互
NPAPI標准提供了一套插件與JS的交互機制。
1. javascript調用插件方法: 瀏覽器首先會調用NPP_GetValue(NPP instance, NPPVariable variable, void* value)取得NPObject對象的地址。Variable參數為NPPVpluginScriptableNPObject。在取得該對象后瀏覽器就可以調用插件提供的NPClass函數。最主要的函數有下面幾個: pluginHasMethod :詢問插件是否支持某一js方法。 pluginHasProperty :詢問插件是否具有某一屬性 pluginInvoke : 當插件支持某一方法時,瀏覽器將會調用該函數執行插件為js提供的這一方法。那么對於提供的很多方法插件如何在該函數內區分。分析這個函數: pluginInvoke(NPObject *obj, NPIdentifier name, const NPVariant *args, uint32_t argCount, NPVariant *result); obj是插件里的NPObject對象地址。 Name表示插件提供方法的名字,通過對比這個參數來區分插件提供的不同方法。 args和argcount分別表示js傳來的參數地址和參數個數。 result是函數返回給js的結果。
2. 插件調用js內部的回調函數: Js可以通過2種方式為插件設置回調函數。示例如下: <script language=javascript> Plugin.Onfun = fun;//方式一 通過設置插件屬性傳入回調函數地址 Plugin.Onfun(fun);//方式二 通過調用插件函數傳入回調函數地址 Function fun(){} < /script>
在插件內部,當js函數地址傳到插件時,瀏覽器把它封裝為一個NPObject對象,里面存有函數地址 方式一: 在插件內部,瀏覽器會調用pluginHasproperty確認插件是否有該屬性。如果有然后瀏覽器調用pluginSetproperty函數。pluginSetProperty(NPObject *obj, NPIdentifier name, const NPVariant *variant)的第二個參數判斷是哪個屬性,第三個參數就是NPObject對象地址。 方式二:在插件內部,瀏覽器會調用pluginHasmethod確定是否支持該方法。然后調用pluginInvoke,這里面的args參數包含了回調函數NPObject地址。 js設置完回調函數后,插件就可以調用該函數了。需要使用NPN_InvokeDefault,示例代碼如下: bool bret = gBrowser->invokeDefault(npp, callbackNPObject, &pV, 1, &result);
另外,插件也可以直接調用js中的函數。在插件內部調用瀏覽器的getUrl函數。具體格式如下: gBrowser->geturl(inst(), “javascript:function()”, "_self"); 如果想傳入整數參數,上面函數第二個參數應寫成: “javascript:function(“+num+”)”。 如果傳入字符串參數,上面函數第二個參數為: “javascript:function(/’“+”string”+”/’)”。如果字符串含有中文,需要進行url encode。
8.開發Netscape插件的一般方法
盡管開發netscape插件有不少可用框架。但是開發一個典型的netscape插件主要要做的工作有以下:
1.編寫一個動態鏈接庫。 插件的表現形式就是一個動態鏈接庫,所以首先編寫一個dll文件或者.so文件供瀏覽器調用。注意,插件的名稱應該以np開頭,NPAPI標准的默認規則。
2.添加dll導出接口。 在xx.def文件中添加dll的導出函數,如圖
上面導出了三個NP_函數,供瀏覽器調用。這三個函數的作用前面已經說過,不再細述
3.添加插件的mimetype。首先為插件添加一個.rc文件,注意該資源文件的默認語言應該是英文,簡體中文的話瀏覽器識別不出,然后在.rc文件中添加mimetype屬性。
如圖
4.實現npapi.h中聲明的NPP函數。
5.在Np_GetEntryPoints中將NPP_函數地址賦值給傳出參數。
6..在NP_Initialize中保存傳入的NPNetscapeFuncs對象指針,供插件后續調用
7.在Np_Shutdown中處理反初始化操作。
8.對NPClass中的函數進行實現
