NPAPI——實現非IE瀏覽器的相似ActiveX的本地程序(插件)調用

一.Netscape Plugin Interface(NPAPI)javascript

大體的說明能夠看下官方文檔Pluginhtml

本文主要針對於JavaScript與插件交互部分作一些交流,好比用於數字證書的操做(淘寶和支付寶的插件),用於播放的flash player插件等java

與javascript的交互須要用到NPAPI中的npruntime Scripting pluginsgit

下面的部分將以示例的方式說明整個過程如何去實現github

 

在開始前須要從火狐瀏覽器源代碼中獲取接口頭文件火狐4.0.1源碼下載docker

下載後在\firefox-4.0.1.source\mozilla-2.0\modules\plugin能夠找到一些samples和頭文件windows

這裏爲方便下載,上傳了一份單獨的plugin文件夾api

 

另外,基於NPAPI的一個跨瀏覽器插件開發的框架FireBreath,很是容易上手並且聽說跨瀏覽器的支持很是好,可是很是笨重,有些功能不須要的也不太容易去掉數組

Firebreath,有興趣的能夠去了解下,Firebreath的源代碼也能夠做爲基於NPAPI開發的一些參考瀏覽器

 

還有一個基於NPAPI作的簡單的示例,結構很是簡單,不用繞來繞去,相對理解起來也簡單許多

npsimple

二.插件入門開發的示例 

開發工具爲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資源文件

 

[plain]  view plain  copy
 
 print?
  1. // Microsoft Visual C++ generated resource script.  
  2. //  
  3. #include "resource.h"  
  4.   
  5. #define APSTUDIO_READONLY_SYMBOLS  
  6. /////////////////////////////////////////////////////////////////////////////  
  7. //  
  8. // Generated from the TEXTINCLUDE 2 resource.  
  9. //  
  10. #include "afxres.h"  
  11.   
  12. /////////////////////////////////////////////////////////////////////////////  
  13. #undef APSTUDIO_READONLY_SYMBOLS  
  14.   
  15. /////////////////////////////////////////////////////////////////////////////  
  16. // Chinese (Simplified, PRC) resources  
  17.   
  18. #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS)  
  19. LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED  
  20.   
  21. #ifdef APSTUDIO_INVOKED  
  22. /////////////////////////////////////////////////////////////////////////////  
  23. //  
  24. // TEXTINCLUDE  
  25. //  
  26.   
  27. 1 TEXTINCLUDE   
  28. BEGIN  
  29.     "resource.h\0"  
  30. END  
  31.   
  32. 2 TEXTINCLUDE   
  33. BEGIN  
  34.     "#include ""afxres.h""\r\n"  
  35.     "\0"  
  36. END  
  37.   
  38. 3 TEXTINCLUDE   
  39. BEGIN  
  40.     "\r\n"  
  41.     "\0"  
  42. END  
  43.   
  44. #endif    // APSTUDIO_INVOKED  
  45.   
  46.   
  47. /////////////////////////////////////////////////////////////////////////////  
  48. //  
  49. // Version  
  50. //  
  51.   
  52. VS_VERSION_INFO VERSIONINFO  
  53.  FILEVERSION 1,0,0,1  
  54.  PRODUCTVERSION 1,0,0,1  
  55.  FILEFLAGSMASK 0x3fL  
  56. #ifdef _DEBUG  
  57.  FILEFLAGS 0x1L  
  58. #else  
  59.  FILEFLAGS 0x0L  
  60. #endif  
  61.  FILEOS 0x40004L  
  62.  FILETYPE 0x2L  
  63.  FILESUBTYPE 0x0L  
  64. BEGIN  
  65.     BLOCK "StringFileInfo"  
  66.     BEGIN  
  67.         BLOCK "040904e4"  
  68.         BEGIN  
  69.             VALUE "CompanyName", "WHU ISS"  
  70.             VALUE "FileDescription", "A new Plugin For test"  
  71.             VALUE "FileVersion", "1.0.0.1"  
  72.             VALUE "InternalName", "npTest.dll"  
  73.             VALUE "LegalCopyright", "Copyright (C) 2012"  
  74.         VALUE "MIMEType", "application/x-npTest"  
  75.             VALUE "OriginalFilename", "npTest.dll"  
  76.             VALUE "ProductName", "new Plugin Test"  
  77.             VALUE "ProductVersion", "1.0.0.1"  
  78.         END  
  79.     END  
  80.     BLOCK "VarFileInfo"  
  81.     BEGIN  
  82.         VALUE "Translation", 0x804, 1200  
  83.     END  
  84. END  
  85.   
  86. #endif    // Chinese (Simplified, PRC) resources  
  87. /////////////////////////////////////////////////////////////////////////////  
  88.   
  89.   
  90.   
  91. #ifndef APSTUDIO_INVOKED  
  92. /////////////////////////////////////////////////////////////////////////////  
  93. //  
  94. // Generated from the TEXTINCLUDE 3 resource.  
  95. //  
  96.   
  97.   
  98. /////////////////////////////////////////////////////////////////////////////  
  99. #endif    // not APSTUDIO_INVOKED  

須要注意的是Block 必須爲040904e4,MIMEType爲最後引用插件的標誌

 

4.新建一個Module-Definition File(.def),定義入口函數

 

[plain]  view plain  copy
 
 print?
  1. LIBRARY   npTest  
  2.   
  3. EXPORTS  
  4.     NP_GetEntryPoints   @1  
  5.     NP_Initialize       @2  
  6.     NP_Shutdown         @3  

 


5.新建一個CPlugin類繼承nsPluginInstanceBase,做爲插件實例類(後面再說該類的做用)

肯定以後,在plugin.h中#include <pluginbase.h>

類名爲Cplugin,頭文件名爲plugin.h,(npp_gate.cpp會使用到,不一樣能夠修改)

修改構造函數的實現,帶參數NPP類型並新建一個屬性保存該參數

實現父類的三個純虛函數

 

[cpp]  view plain  copy
 
 print?
  1. NPBool init(NPWindow* aWindow);//NPWindow用於插件中繪畫部件的窗口  
  2. void shut();  
  3. 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

添加靜態方法,用於建立該腳本操做的對象

 

[cpp]  view plain  copy
 
 print?
  1. public:  
  2.     static NPObject* _allocate(NPP npp,NPClass* aClass);  
  3.     static void _deallocate(NPObject *npobj);  
  4.     static void _invalidate(NPObject *npobj);  
  5.     static bool _hasMethod(NPObject* obj, NPIdentifier methodName);  
  6.     static bool _invokeDefault(NPObject *obj, const NPVariant *args, uint32_t argCount, NPVariant *result);  
  7.     static bool _invoke(NPObject* obj, NPIdentifier methodName, const NPVariant *args, uint32_t argCount, NPVariant *result);  
  8.     static bool _hasProperty(NPObject *obj, NPIdentifier propertyName);  
  9.     static bool _getProperty(NPObject *obj, NPIdentifier propertyName, NPVariant *result);  
  10.     static bool _setProperty(NPObject *npobj, NPIdentifier name,const NPVariant *value);  
  11.     static bool _removeProperty(NPObject *npobj, NPIdentifier name);  
  12.     static bool _enumerate(NPObject *npobj, NPIdentifier **identifier,uint32_t *count);  
  13.     static bool _construct(NPObject *npobj, const NPVariant *args,uint32_t argCount, NPVariant *result);  

 

在PluginObject.h中聲明一個NPClass對象,使用上面的靜態方法將該NPClass對象初始化

 

[cpp]  view plain  copy
 
 print?
  1. #ifndef __object_class  
  2. #define __object_class  
  3. static NPClass objectClass = {  
  4. NP_CLASS_STRUCT_VERSION,  
  5. PluginObject::_allocate,  
  6. PluginObject::_deallocate,  
  7. PluginObject::_invalidate,  
  8. PluginObject::_hasMethod,  
  9. PluginObject::_invoke,  
  10. PluginObject::_invokeDefault,  
  11. PluginObject::_hasProperty,  
  12. PluginObject::_getProperty,  
  13. PluginObject::_setProperty,  
  14. PluginObject::_removeProperty,  
  15. PluginObject::_enumerate,  
  16. PluginObject::_construct  
  17. };  
  18. #endif  
回到第6步中在CPlugin類中實現的GetScriptableObject方法

 

在該方法中經過NPNCreateObject方法建立該對象

 

[cpp]  view plain  copy
 
 print?
  1. NPObject* CPlugin::GetScriptableObject(){  
  2.     return NPN_CreateObject(this->instance,&objectClass);  
  3. }  
this->instance在構造函數時獲取並保存下來的NPP對象.

 

NPN_CreateObject會在瀏覽器中作一些操做而後回來調用objectClass中的_allocate方法

須要實現該靜態方法,new 一個PluginObject

新建一個NPP npp屬性,和一個NPP參數的構造函數

 

[cpp]  view plain  copy
 
 print?
  1. NPObject* PluginObject::_allocate(NPP npp,NPClass* aClass){  
  2.     return new PluginObject(npp);  
  3. }  

 

後面的操做中,瀏覽器調用了NPNFunc中以上的一些方法則會來調用這些靜態方法,並將_allocate返回的值做爲參數傳到其餘函數中

接下來的實現就相對比較隨意了,能夠直接在這些靜態方法中實現想要的效果,

也能夠在PluginObject中建立對應的成員函數實現,而後在靜態方法中經過nobj參數轉換爲(PluginObject)類型調用相應成員函數


其中幾個函數比較重要,_hasMethod判斷參見是否有該函數,_getProperty則是判斷屬性,invoke調用相應方法,

invokeDefault能夠在invoke中調用NPN_InvokeDefault來訪問,最好不要直接調用,(見API,緣由未知,通常瀏覽器都要作進一步操做)

 

hasMethod等方法的相似於參數methodName都是以identifier做爲判斷的,能夠調用NPN_GetStringIdentifier獲取

例如:

 

[cpp]  view plain  copy
 
 print?
  1. PluginObject::PluginObject(NPP npp)  
  2. {  
  3.       
  4.     this->npp = npp;  
  5.     id_func_add = NPN_GetStringIdentifier("add");  
  6.     id_property_version = NPN_GetStringIdentifier("version");  
  7. }  
  8. bool PluginObject::hasMethod(NPObject* obj, NPIdentifier methodName)  
  9. {  
  10.   
  11.     if(methodName==this->id_func_add)  
  12.         return true;  
  13.     return false;  
  14. }  

 

多說下enumerate方法或者說是NPN_XXX的方法,由於就這個東西折騰我完完整整的兩天時間...

enumerate方法的參數有個指針數組,可是他的結構是

並且初始化的時候必定要用NPN_MemAlloc來操做....API上只有關於指針數組的結構說明,並且很簡單的提了一句,折騰兩天才發現非得用NPN來分配內存- -||

弱弱的總結下,應該是須要給Firefox用到的東西或者說從參數傳進來須要你分配內存的都得用NPN_MemAlloc分配內存

若是出現Access Violation,首先想到什麼地方應該用NPN_MemAlloc....

 

[cpp]  view plain  copy
 
 print?
  1. bool PluginObject::enumerate(NPIdentifier **identifier,uint32_t *count)  
  2. {  
  3.         *count = 1;  
  4.         NPIdentifier *outList(NULL);  
  5.     outList = (NPIdentifier*)NPN_MemAlloc((uint32_t)(sizeof(NPIdentifier) * *count));  
  6.         outList[0] = id_property_version;  
  7.         *identifier = outList;  
  8.         return true;  
  9.   
  10. }  
測試的時候在firebug的控制檯輸入 plugin. 就會去調用enumerate了

 

三.註冊及安裝

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]  view plain  copy
 
 print?
  1. <html>  
  2.     <head>  
  3.         <script>  
  4.             window.onready = function(){  
  5.   
  6.             }  
  7.             function toDoSt(){  
  8.                 var plugin = document.getElementById("plugin");  
  9.                 alert(plugin.version);  
  10.             }  
  11.         </script>  
  12.         <embed id="plugin" type="application/x-npTest" src="file:///path to npTest.dll" pluginspage="http://xxxx">  
  13.     </head>  
  14.     <body>  
  15.         <input type="button" onclick="toDoSt()" value="test">  
  16.     </body>  
  17. </html>  
其中embed的src和pluginspage無關緊要

 

 

五.調試插件

先前一直弄錯了,覺得是指向Firefox.exe,查了很久,發現原來在Firefox4以後新建了一個plugin-Container.exe進程

調試目標指向plugin-container.exe 或者 tools->attach to process選中plugin-container.exe進程 或者debug->attach to process

 

六.附上示例工程

npTest工程下載地址

打開工程後須要修改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

相關文章
相關標籤/搜索