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

一.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做的簡單的示例,結構非常簡單,不用繞來繞去,相對理解起來也簡單許多

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資源文件

  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),定義入口函數

  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類型並新建一個屬性保存該參數

實現父類的三個純虛函數

  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

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

  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對象初始化

  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方法創建該對象

  1. NPObject* CPlugin::GetScriptableObject(){  
  2.     return NPN_CreateObject(this->instance,&objectClass);  
  3. }  

this->instance在構造函數時獲取並保存下來的NPP對象.

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

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

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

  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獲取

例如:

  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....

  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中按照上面的位置給添加上相應信息即可


四.使用插件

  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


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章