一.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资源文件
// 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
4.新建一个Module-Definition File(.def),定义入口函数
LIBRARY npTest
EXPORTS
NP_GetEntryPoints @1
NP_Initialize @2
NP_Shutdown @3
5.新建一个CPlugin类继承nsPluginInstanceBase,作为插件实例类(后面再说该类的作用)
确定之后,在plugin.h中#include
类名为Cplugin,头文件名为plugin.h,(npp_gate.cpp会使用到,不同可以修改)
修改构造函数的实现,带参数NPP类型并新建一个属性保存该参数
实现父类的三个纯虚函数
NPBool init(NPWindow* aWindow);//NPWindow用于插件中绘画部件的窗口
void shut();
NPBool isInitialized();
从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
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
因为使用到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中按照上面的位置给添加上相应信息即可
四.使用插件
其中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