一.Netscape Plugin Interface(NPAPI)
大致的說明可以看下官方文檔Plugin
本文主要針對於JavaScript與插件交互部分做一些交流,比如用於數字證書的操作(淘寶和支付寶的插件),用於播放的flash player插件等
與javascript的交互需要用到NPAPI中的npruntime Scripting plugins
下面的部分將以示例的方式說明整個過程如何去實現
在開始前需要從火狐瀏覽器源代碼中獲取接口頭文件火狐4.0.1源碼下載
下載后在\firefox-4.0.1.source\mozilla-2.0\modules\plugin可以找到一些samples和頭文件
這里為方便下載,上傳了一份單獨的plugin文件夾
另外,基於NPAPI的一個跨瀏覽器插件開發的框架FireBreath,非常容易上手而且據說跨瀏覽器的支持非常好,但是非常笨重,有些功能不需要的也不太容易去掉
Firebreath,有興趣的可以去了解下,Firebreath的源代碼也可以作為基於NPAPI開發的一些參考
還有一個基於NPAPI做的簡單的示例,結構非常簡單,不用繞來繞去,相對理解起來也簡單許多
二.插件入門開發的示例
開發工具為visual studio 2010
1.新建一個Win32 project,命名以np開頭(目的是編譯完的Dll名必須以np開頭才能被識別為插件)
類型為一個DLL的空工程即可
2.右鍵選中項目的屬性,在VC++ Directories目錄下,選擇Include Directories,Edit,
將plugin/base/public和plugin/sdk/samples/include添加到include
3.新建Version資源文件
- // Microsoft Visual C++ generated resource script.
- //
- #include "resource.h"
- #define APSTUDIO_READONLY_SYMBOLS
- /////////////////////////////////////////////////////////////////////////////
- //
- // Generated from the TEXTINCLUDE 2 resource.
- //
- #include "afxres.h"
- /////////////////////////////////////////////////////////////////////////////
- #undef APSTUDIO_READONLY_SYMBOLS
- /////////////////////////////////////////////////////////////////////////////
- // Chinese (Simplified, PRC) resources
- #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS)
- LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED
- #ifdef APSTUDIO_INVOKED
- /////////////////////////////////////////////////////////////////////////////
- //
- // TEXTINCLUDE
- //
- 1 TEXTINCLUDE
- BEGIN
- "resource.h\0"
- END
- 2 TEXTINCLUDE
- BEGIN
- "#include ""afxres.h""\r\n"
- "\0"
- END
- 3 TEXTINCLUDE
- BEGIN
- "\r\n"
- "\0"
- END
- #endif // APSTUDIO_INVOKED
- /////////////////////////////////////////////////////////////////////////////
- //
- // Version
- //
- VS_VERSION_INFO VERSIONINFO
- FILEVERSION 1,0,0,1
- PRODUCTVERSION 1,0,0,1
- FILEFLAGSMASK 0x3fL
- #ifdef _DEBUG
- FILEFLAGS 0x1L
- #else
- FILEFLAGS 0x0L
- #endif
- FILEOS 0x40004L
- FILETYPE 0x2L
- FILESUBTYPE 0x0L
- BEGIN
- BLOCK "StringFileInfo"
- BEGIN
- BLOCK "040904e4"
- BEGIN
- VALUE "CompanyName", "WHU ISS"
- VALUE "FileDescription", "A new Plugin For test"
- VALUE "FileVersion", "1.0.0.1"
- VALUE "InternalName", "npTest.dll"
- VALUE "LegalCopyright", "Copyright (C) 2012"
- VALUE "MIMEType", "application/x-npTest"
- VALUE "OriginalFilename", "npTest.dll"
- VALUE "ProductName", "new Plugin Test"
- VALUE "ProductVersion", "1.0.0.1"
- END
- END
- BLOCK "VarFileInfo"
- BEGIN
- VALUE "Translation", 0x804, 1200
- END
- END
- #endif // Chinese (Simplified, PRC) resources
- /////////////////////////////////////////////////////////////////////////////
- #ifndef APSTUDIO_INVOKED
- /////////////////////////////////////////////////////////////////////////////
- //
- // Generated from the TEXTINCLUDE 3 resource.
- //
- /////////////////////////////////////////////////////////////////////////////
- #endif // not APSTUDIO_INVOKED
需要注意的是Block 必須為040904e4,MIMEType為最后引用插件的標志
4.新建一個Module-Definition File(.def),定義入口函數
- LIBRARY npTest
- EXPORTS
- NP_GetEntryPoints @1
- NP_Initialize @2
- NP_Shutdown @3
5.新建一個CPlugin類繼承nsPluginInstanceBase,作為插件實例類(后面再說該類的作用)
確定之后,在plugin.h中#include <pluginbase.h>
類名為Cplugin,頭文件名為plugin.h,(npp_gate.cpp會使用到,不同可以修改)
修改構造函數的實現,帶參數NPP類型並新建一個屬性保存該參數
實現父類的三個純虛函數
- NPBool init(NPWindow* aWindow);//NPWindow用於插件中繪畫部件的窗口
- void shut();
- NPBool isInitialized();
6.免得做過多操作,從samples中引入已經編寫好的入口函數
從plugin\sdk\samples\npruntime路徑添加np_entry.cpp(插件入口函數),npn_gate.cpp(插件調用瀏覽器的一些方法),npp_gate.cpp(瀏覽器調用插件的一些方法)
添加后需要做一點修改,
1).np_entry.cpp和npn_gate.cpp的引用
#include "npapi.h"
#include "npfunctions.h"
換成
#include<pluginbase.h>
2).然后進入pluginbase.h,再進入npplat.h,將
#ifdef XP_WIN
#include "windows.h"
#endif
挪到
#include "npapi.h"
#include "npfunctions.h"
前面,
3).然后在項目屬性,Preprocessor,Preprocessor Definitions添加XP_WIN的定義
(這樣做的原因是windows.h需要在npapi.h前定義,自己在所有引用了npapi.h的前面加上windows.h的引用也可以)
4),np_entry.cpp中引入頭文件#include <stddef.h>
因為使用到offsetof
這三個文件中的函數非常重要,首先來看下np_entry.cpp中的函數
NP_GetEntryPoints函數,為插件入口的函數,插件初始化將會首先調用該函數
該函數用於初始化瀏覽器調用插件的函數表,以NPP(np plugin)開頭,
后面的插件的一些事件(new等)發生時將會以這里初始化的函數作為入口,比如
pFuncs->newp = NPP_New;初始化后將會在創建插件實例時調用NPP_New的實現來創建.
NP_Initialize函數,初始化插件時,在NP_GetEntryPoints后調用,
該函數用於初始化插件調用瀏覽器的函數表,參數pFuncs帶有該函數表信息,
我們自定義一個對象保存這些信息,今后就可通過該對象調用方法來實現對瀏覽器的一些操作
NP_Shutdown函數,與NP_Initialize對應,主要釋放資源等操作
再來看下npp_gate.cpp,這個文件中的函數都以NPP開頭,用於定義瀏覽器調用插件的方法
經過NP_GetEntryPoints的初始化后,當特定事件發生時,瀏覽器將會調用這些方法
然后需要注意的是該文件引用了plugin.h,是我們第5步創建的文件,名字不同可以改改
NPP_New方法,用於創建插件實例
CPlugin * pPlugin = new CPlugin(instance);這句話為創建一個我們定義的CPlugin類對象,構造函數為NPP類型
NPP_Destroy方法,用於銷毀插件實例,刷新頁面,關閉頁面等操作會觸發
該方法會調用CPlugin的shut方法再delete掉實例
NPP_SetWindow方法,插件窗口發生任何變化都會調用該方法
window創建時會調用一次,如果初始化失敗則delete掉實例然后返回錯誤
NPP_GetValue方法,當獲取插件有關的一些信息時會觸發該方法調用(如獲取插件名,插件實例)
當javascript操作插件對象時,該方法調用CPlugin的GetScriptableObject方法,需要自己實現,返回一個腳本操作對象(NPObject)
在這里返回到CPlugin類,添加GetScriptableObject方法並實現(見第7步操作)
NPP_HandleEvent方法,處理事件,該方法調用CPlugin的handleEvent方法,繼續添加實現吧
該文件中其他方法暫時沒什么可說的,需要用到的可以查下API並實現出來就行了.
再看下npn_gate.cpp,該文件實現了對瀏覽器的一些操作的函數,都以NPN(np netscape)開頭
其中有一些帶有NPObject*參數的與GetScriptableObject方法創建的腳本操作對象有關,將在第7步做說明
該文件中用到的NPNetscapeFuncs NPNFuncs;在NP_Initialize中初始化完成
7.封裝一個腳本操作對象
Add一個C++類,該示例命名為PluginObject,繼承NPObject
添加靜態方法,用於創建該腳本操作的對象
- public:
- static NPObject* _allocate(NPP npp,NPClass* aClass);
- static void _deallocate(NPObject *npobj);
- static void _invalidate(NPObject *npobj);
- static bool _hasMethod(NPObject* obj, NPIdentifier methodName);
- static bool _invokeDefault(NPObject *obj, const NPVariant *args, uint32_t argCount, NPVariant *result);
- static bool _invoke(NPObject* obj, NPIdentifier methodName, const NPVariant *args, uint32_t argCount, NPVariant *result);
- static bool _hasProperty(NPObject *obj, NPIdentifier propertyName);
- static bool _getProperty(NPObject *obj, NPIdentifier propertyName, NPVariant *result);
- static bool _setProperty(NPObject *npobj, NPIdentifier name,const NPVariant *value);
- static bool _removeProperty(NPObject *npobj, NPIdentifier name);
- static bool _enumerate(NPObject *npobj, NPIdentifier **identifier,uint32_t *count);
- static bool _construct(NPObject *npobj, const NPVariant *args,uint32_t argCount, NPVariant *result);
在PluginObject.h中聲明一個NPClass對象,使用上面的靜態方法將該NPClass對象初始化
- #ifndef __object_class
- #define __object_class
- static NPClass objectClass = {
- NP_CLASS_STRUCT_VERSION,
- PluginObject::_allocate,
- PluginObject::_deallocate,
- PluginObject::_invalidate,
- PluginObject::_hasMethod,
- PluginObject::_invoke,
- PluginObject::_invokeDefault,
- PluginObject::_hasProperty,
- PluginObject::_getProperty,
- PluginObject::_setProperty,
- PluginObject::_removeProperty,
- PluginObject::_enumerate,
- PluginObject::_construct
- };
- #endif
在該方法中通過NPNCreateObject方法創建該對象
- NPObject* CPlugin::GetScriptableObject(){
- return NPN_CreateObject(this->instance,&objectClass);
- }
NPN_CreateObject會在瀏覽器中做一些操作然后回來調用objectClass中的_allocate方法
需要實現該靜態方法,new 一個PluginObject
新建一個NPP npp屬性,和一個NPP參數的構造函數
- NPObject* PluginObject::_allocate(NPP npp,NPClass* aClass){
- return new PluginObject(npp);
- }
后面的操作中,瀏覽器調用了NPNFunc中以上的一些方法則會來調用這些靜態方法,並將_allocate返回的值作為參數傳到其他函數中
接下來的實現就相對比較隨意了,可以直接在這些靜態方法中實現想要的效果,
也可以在PluginObject中創建對應的成員函數實現,然后在靜態方法中通過nobj參數轉換為(PluginObject)類型調用相應成員函數
其中幾個函數比較重要,_hasMethod判斷參見是否有該函數,_getProperty則是判斷屬性,invoke調用相應方法,
invokeDefault可以在invoke中調用NPN_InvokeDefault來訪問,最好不要直接調用,(見API,原因未知,一般瀏覽器都要做進一步操作)
hasMethod等方法的類似於參數methodName都是以identifier作為判斷的,可以調用NPN_GetStringIdentifier獲取
例如:
- PluginObject::PluginObject(NPP npp)
- {
- this->npp = npp;
- id_func_add = NPN_GetStringIdentifier("add");
- id_property_version = NPN_GetStringIdentifier("version");
- }
- bool PluginObject::hasMethod(NPObject* obj, NPIdentifier methodName)
- {
- if(methodName==this->id_func_add)
- return true;
- return false;
- }
多說下enumerate方法或者說是NPN_XXX的方法,因為就這個東西折騰我完完整整的兩天時間...
enumerate方法的參數有個指針數組,但是他的結構是
而且初始化的時候一定要用NPN_MemAlloc來操作....API上只有關於指針數組的結構說明,而且很簡單的提了一句,折騰兩天才發現非得用NPN來分配內存- -||
弱弱的總結下,應該是需要給Firefox用到的東西或者說從參數傳進來需要你分配內存的都得用NPN_MemAlloc分配內存
如果出現Access Violation,首先想到什么地方應該用NPN_MemAlloc....
- bool PluginObject::enumerate(NPIdentifier **identifier,uint32_t *count)
- {
- *count = 1;
- NPIdentifier *outList(NULL);
- outList = (NPIdentifier*)NPN_MemAlloc((uint32_t)(sizeof(NPIdentifier) * *count));
- outList[0] = id_property_version;
- *identifier = outList;
- return true;
- }
三.注冊及安裝
1.注冊表注冊位置
HKEY_CURRENT_USER\Software\MozillaPlugins
添加一個項@whuiss.com/npTest
添加字符串值
"Description"="code project test"
"Path"="path to npTest.dll"
"ProductName"="npdemo Dynamic Library"
"Vendor"="zsy"
"Version"="1.0.0.1"
添加子項MIMETypes
添加MIMETypes的子項application/x-npTest
但是實際上只需要一個項@whuiss.com/npTest以及一個Path字符串值,其他可有可無
在firefox地址欄輸入about:plugins可查到你的插件了
2.使用安裝文件注冊
visual studio新建一個set up project
FileSystem View中選中dll或者某個工程的輸出
Registry View中按照上面的位置給添加上相應信息即可
四.使用插件
- <html>
- <head>
- <script>
- window.onready = function(){
- }
- function toDoSt(){
- var plugin = document.getElementById("plugin");
- alert(plugin.version);
- }
- </script>
- <embed id="plugin" type="application/x-npTest" src="file:///path to npTest.dll" pluginspage="http://xxxx">
- </head>
- <body>
- <input type="button" onclick="toDoSt()" value="test">
- </body>
- </html>
五.調試插件
先前一直弄錯了,以為是指向Firefox.exe,查了好久,發現原來在Firefox4之后新建了一個plugin-Container.exe進程
調試目標指向plugin-container.exe 或者 tools->attach to process選中plugin-container.exe進程 或者debug->attach to process
六.附上示例工程
打開工程后需要修改include directory
------------------------------------------------------分割線-----------------------------------------------------
發現個新問題,NPAPI執行函數返回值不支持帶中文的么?
調試很多次了,也不知道是配置問題還是什么問題,NPVariant *result中帶有值返回的
但是到瀏覽器就變成空字符串,去掉中文的就能正常顯示
Firebreath的也試過了,也不支持中文字符
沒辦法,只好將返回的值轉成base64再在瀏覽器解碼,這樣倒是可以正常
------------------------------------------------------分割線-----------------------------------------------------
firefox新版本 彈出winform(例如訪問某些智能卡私鑰會需要輸入PIN)的時候導致假死的情況,在火狐社區提問了,能夠解決
http://mozilla.com.cn/post/31422/#reply-24747
大致意思就是修改config里面的dom.ipc.plugins.enabled.your-plugin.dll=false
from:http://blog.csdn.net/hzzhoushaoyu/article/details/7387516