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做的简单的示例,结构非常简单,不用绕来绕去,相对理解起来也简单许多
二.插件入门开发的示例
开发工具为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
回到第6步中在CPlugin类中实现的GetScriptableObject方法
在该方法中通过NPNCreateObject方法创建该对象
- NPObject* CPlugin::GetScriptableObject(){
- return NPN_CreateObject(this->instance,&objectClass);
- }
this->instance在构造函数时获取并保存下来的NPP对象.
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;
- }
测试的时候在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>
- <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>
其中embed的src和pluginspage可有可无
五.调试插件
先前一直弄错了,以为是指向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
NPAPI——实现非IE浏览器的类似ActiveX的本地程序(插件)调用的更多相关文章
- 非IE内核浏览器如何支持activex插件
原文地址:https://blog.csdn.net/johnson2008t/article/details/46126605 之前在一个B/S项目中遇到一个需求,就是客户需要在页面上对报表的布局以 ...
- ASP.NET MVC 使用Jquery Uploadify 在非IE浏览器下Http Error的解决方案
解决Uploadify上传控件在非IE浏览器中不工作,需要做如下2步修改: 1.Global.asax文件中,实现Application_BeginRequest函数: void Applicatio ...
- VS2010在非IE浏览器下调试Silverlight程序
以Chrome为例: 第一步:在程序中设置断点. 第二步:右键点击web应用程序的起始页(.html或.aspx文件),选择"浏览方式",选中Chrome或其它非IE浏览器,点&q ...
- CSS hack 如何区分所有IE浏览器和非IE浏览器
网上方法很多,例如,测试后得出以下结论,多余的话不说了,直入主题: 1.所有的推理IE浏览器 正解:此写法仅仅被lE浏览器识别,非IE浏览器不识别. <!--[if IE]> <st ...
- 黄聪:微信h5支付demo微信H5支付demo非微信浏览器支付demo微信wap支付
一.首先先确定H5支付权限已经申请! 二.开发流程 1.用户在商户侧完成下单,使用微信支付进行支付 2.由商户后台向微信支付发起下单请求(调用统一下单接口)注:交易类型trade_type=MWEB ...
- 兼容IE9以下和非IE浏览器的原生js事件绑定函数
事件绑定函数的demo如下: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "htt ...
- H5版如何在微信外(非微信浏览器)进行微信支付技术方案
官方是支持在非微信内置浏览器中调起微信支付的!H5支付是基于公众号基础开发的一种非微信内浏览器支付方式(需要单独申请支付权限),可以满足在微信外的手机H5页面进行微信支付的需求.同时,由于H5链接传播 ...
- (转)如何让ActiveXObject( "Microsoft.XmlDom ")对象在非IE浏览器下显示数据?firefox(火狐)
如何让ActiveXObject( "Microsoft.XmlDom ")对象在非IE浏览器下显示数据?firefox(火狐) 2013-09-10 16:01 2152人阅读 ...
- 非ie浏览器必备函数常识
场景描述: 我们都知道IE浏览器和非IE浏览器都有很多功能一样但写法不同,或者各自都有一些自己独特的方法,那么为了保持兼容性和便于编写,我们可以通过这两个方法给非IE浏览器的对象增加自己没有,但IE有 ...
随机推荐
- unity3d 让物体移动到点击位置
using UnityEngine; using System.Collections; public class test : MonoBehaviour { //在场景中鼠标点击地面后,角色可以移 ...
- hiho一下 第174周
题目1 : Dice Possibility 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 What is possibility of rolling N dice ...
- <a>和<table>标签的应用
今天介绍一下html中最重要的标签 标签分为 1.一般标签 如<img> <b></b>等 2.功能标签 如<a></a> 3.实体 如&a ...
- 使用JDK和axis2发布webservice
最近使用webservice进行远程调用一直很火,自从JDK1.6版本发布后,发布一个webservice项目变得更加简单了 笔者由于工作的需要针对JDK和axis2如何发布webservice做过相 ...
- Centos7下git服务器及gogs部署
1.安装git # yum install -y git 2.创建git用户及组 # groupadd git # adduser git -g git # mkdir /home/git # mkd ...
- 3D集合图元:最小边界框/包围盒(boundingbox)
对于2D边界框的应用时比较广泛地,它为一个简单匹配建立了很小的计算规则,3D模型的boundingbox则比较困难,计算代价较大.对于PCL库的使用则降低了计算难度,三维数值化降低了建模过程,可以使用 ...
- Integer Intervals POJ - 1716_查分约束_
Code: #include<cstdio> #include<queue> #include<algorithm> using namespace std; co ...
- 洛谷P1231 教辅的组成 最大流
裸题… Code: #include<cstdio> #include<cstring> #include<algorithm> #include<vecto ...
- 训练1-H
小明今年3岁了, 现在他已经能够认识100以内的非负整数, 并且能够进行100以内的非负整数的加法计算. 对于大于等于100的整数, 小明仅保留该数的最后两位进行计算, 如果计算结果大于等于100, ...
- redis_2 数据类型
1.key Redis keys 命令 下表给出了与 Redis 键相关的基本命令: 序号 命令及描述 1 DEL key该命令用于在 key 存在时删除 key. 2 DUMP key 序列化给定 ...