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

相关文章
相关标签/搜索