使用NPAPI开发Fireforx/Chrome插件
一、编译runtime
1、Download SDK:
ftp://ftp.mozilla.org/pub/mozilla.org/firefox/releases/4.0.1/source/ 下载解压firefox-4.0.1.source.tar.bz2文件
解压的文件夹firefox-4.0.1.source\mozilla-2.0\modules\plugin,里面有开发NPAPI插件所需的所有资源.
2、在plugin目录下面的sdk\samples可以找到npruntime的例子,接着根据文档说明,一步一步建立vs2008工程
我的路径是桌面下c:\Users\Administrator\Desktop\firefox-4.0.1.source\mozilla-2.0\modules\plugin\sdk\samples\npruntime
首先: 使用VS2008创建空的win32 dll项目,然后设置工程属性:
步骤一:添加npruntime文件夹下面所有的.h,.cpp文件,还有.rc,.def文件
步骤二:工程设置库路径: 工程属性->c/c++常规:附加包含目录 ../../include;../../../../base/public
步骤三:工程设置预处理器:工程属性->c/c++->预处理器:预处理器定义:WIN32;_DEBUG;_WINDOWS;_USRDLL;NPRUNTIME_EXPORTS;XP_WIN32;XP_WIN;_X86_
步骤四:工程设置预编译头:工程属性->c/c++->预编译头:创建/使用预编译头:不使用预编译头
步骤五:建立导出文件声明:def,指定导出接口
步骤六:将.rc文件里面的FileDescrip,fileOpenName,internalName,OrigianalName都改成与工程统一的名称
开始编译:一堆错误,主要是一下几个
error C3861: 'printf': identifier not found
error C2664: 'DrawTextW' : cannot convert parameter 2 from 'char [128]' to 'LPCWSTR'
error C2275: 'NPPluginFuncs' : illegal use of this type as an expression
error C2065: 'setvalue' : undeclared identifier
error C3861: 'offsetof': identifier not found
第一个错误:plugin.cpp 不认识printf,由于没使用预编译头,因此加上#include <stdio.h>
第二个错误:编译使用的是unicode字符集,因此DrawText会被按照DrawTextW来编译,可以修改工程属性,使用多字符集编译或者将DrawText改为DrawTextA
第三、四五个错误:setvalue offsetoff缺少,加上#include <stddef.h>
再重新编译,ok,看到了激动的npruntime.dll
参考:https://developer.mozilla.org/en-US/docs/Compiling_The_npruntime_Sample_Plugin_in_Visual_Studio
3、检测fireforx是否识别
将npruntime.dll拷贝到fireforx下的plugins目录下:没装插件貌似没有这个目录,自己新建一个,放进去,打开fireforx,输入about:plugins,看到了
npruntime scriptable example plugin
文件: npruntime.dll
版本: 1.0.0.1
npruntime
MIME 类型 描述 后缀
application/mozilla-npruntime-scriptable-plugin npruntime rts
成功。
4、用fireforx打开npruntime目录下面的test.html目录,奇怪,没加载,打开test.html查看了一下,原来有个地方需要改为:<embed type="application/mozilla-
npruntime-scriptable-plugin" style="display: block; width: 50%; height: 100px;"><br>
插件的MIME 类型是application/mozilla-npruntime-scriptable-plugin,fireforx是根据MEMETYPE来加载的。因此需要改成runtime的mime
再次打开,ok,可以点击页面几个按钮感受一下交互
例子编译完成,有了初步的认识之后,接下来需要熟悉一下code了。
二、建立自己的插件
1、VS2008建立空的dll,工程名称:npdemo
2、将npruntime下的相关.h,.cpp,.def拷贝过来,加入到工程中
3、加入头文件,npruntime同级目录下(firefox-4.0.1.sourc\mozilla-2.0\modules\plugin)include整个文件夹(包含2个.h文件)拷贝到工程目录下面,同时将目录
(firefox-4.0.1.source\mozilla-2.0\modules\plugin\base\public下所有文件拷入到刚才的拷入工程的inlcude的里面
此处就是将需要包含的目录独立到工程目录下来
4、对工程进行设置并且进行编译
参照编译runtime。https://developer.mozilla.org/en-US/docs/Compiling_The_npruntime_Sample_Plugin_in_Visual_Studio
需要注意的是: 目录包含先前拷过来的include就够了,以及编译选项和 resource的version上。当然重点还是version上面
新建resource->version文件:声明dll版本信息。编辑version,在资源文件npdemo.rc右键查看代码,进入编辑Version段
添加VALUE "MIMEType", "application/mozilla-npdemo-plugin",此id是Fireforx识别插件的标识
VALUE "FileExtents", "rts"
保存,关闭。在打开,就会看见了。
此处注意:
.rc文件必须是英文(美国)编码040904E4,打开version
同时选中:Block Header,进行属性编辑。(不是version node的attribute)
Language:英文(美国)
Code Page:Windows 3.1 拉丁语 1 (美国、西欧)版
VALUE "Translation", 0x409, 1252
文档说明:https://developer.mozilla.org/en-US/docs/Gecko_Plugin_API_Reference/Plug-in_Development_Overview
5、编译ok,Fireforx也能发现,编译部分完工,接下来处理与js交互方面。详见npdemo
A:js主动调用插件接口。js进行插件调用接口传递的时候,是通过接口id名称来进行传递的,NPAPI里面的NPIdentifier。
以test接口js调用为例:
js 方面:
- var rtn = embed1.Test(true,456,7777,"abssssss","abcsssss\0"); //函数名称Test 插件提供
首先:是先声明一个全局的接口id,
- static NPIdentifier s_idTest;
然后:在plugin类初始化的时候,通过接口名称获取接口的id:
- s_idTest = NPN_GetStringIdentifier("Test");
其次:在ScriptablePluginObject::HasMethod(NPIdentifier name)里面需要指明由此方法,浏览器调用的时候,会先进入到此查询是否有此方法,然后再进入 ScriptablePluginObject::Invoke去调用接口方法。
- if(name == s_idTest || name == s_idTest1)
- {
- OutputDebugStringA("has method Test test1");
- return true;
- }
再次:在ScriptablePluginObject::Invoke里面进行接口的实现,根据接口名称判断是否是此接口来进行对接口的实现过程
- if(name == s_idTest)
- {
- OutputDebugStringA("Test called\n");
- if(args != NULL && argCount >= 5)
- {
- //fun paramer
- NPVariant npParam1 = args[0];
- NPVariant npParam2 = args[1];
- NPVariant npParam3 = args[2];
- NPVariant npParam4 = args[3];
- NPVariant npParam5 = args[4];
- bool npbParam1 = NPVARIANT_TO_BOOLEAN(npParam1);
- //此处传入2个int类型参数,
- //Fireforx下:npnParam2正常 npnParam3为0
- //Chrome 下:npnParam2为0 npnParam3正常
- //即:传入的int参数 Fireforx下需要从int32取,chrome下需要从double下取
- int32_t npnParam2 = NPVARIANT_TO_INT32(npParam2);
- int32_t npnParam3 = NPVARIANT_TO_DOUBLE(npParam3);
- //传入两个string npsParam5后面增加\0,npsParam4后面不加
- //Fireforx下:都正常
- //chrome 下:加了\0的正常,不加的会有乱码出现
- NPString npsParam4 = NPVARIANT_TO_STRING(npParam4);
- NPString npsParam5 = NPVARIANT_TO_STRING(npParam5);
- //构造string返回值 result
- char *temp = (char *)NPN_MemAlloc(1024);
- if(temp == NULL)
- return false;
- sprintf(temp,"npdemodll:bool:[%d] int[%d] int:[%d] string:[%s] string:[%s]",
- npbParam1,npnParam2,npnParam3,npsParam4.UTF8Characters,npsParam5.UTF8Characters);
- STRINGZ_TO_NPVARIANT(temp,*result);
- //return value
- //DOUBLE_TO_NPVARIANT(2437309816,*result);
- //BOOL bResult = FALSE;
- //BOOLEAN_TO_NPVARIANT(bResult,*result);
- return true;
- }
- }
B:js提供接口给插件调用,即回调。
以jsCallbackFun为例
js方面:
- embed1.jsCallbackFun = jsfunc; //embed1为插件对象,jsCallbackFun回调接口做为插件对象的一个属性,被赋值为js的函数,有插件提供
- function jsfunc(state)
- {
- alert("jsfunc called!");
- alert(state);
- }
插件方面:
同样声明回调接口id:
- static NPIdentifier s_idjsCallbackFun;
同样获取接口名称 :
- s_idjsCallbackFun = NPN_GetStringIdentifier("jsCallbackFun");
不同是:此接口作为属性提供,因此在ScriptablePluginObject::HasProperty需要声明
- bool ScriptablePluginObject::HasProperty(NPIdentifier name)
- {
- return name == s_idjsCallbackFun;
- //return false;
- }
同时也需要在属性设置 ScriptablePluginObject::SetProperty下设置。
需要先在cplugin类下增加一个回调接口对象的成员变量:NPObject *m_pJSCallbackFunObj;,同时提供访问和设置属性接口,m_pJSCallbackFunObj 需要构造为null,不然会崩溃
- bool ScriptablePluginObject::SetProperty(NPIdentifier name,
- const NPVariant *value)
- {
- //js property
- if(name == s_idjsCallbackFun)
- {
- CPlugin *plugin = (CPlugin *)mNpp->pdata;
- if(plugin == NULL)
- return false;
- if(plugin->GetJSCallbackFunObj() == NULL)
- {
- //set js property obj
- NPObject *pObject = NPN_RetainObject(NPVARIANT_TO_OBJECT(*value));
- plugin->SetJSCallbackFunObj(pObject);
- }
- return true;
- }
- return false;
- }
然后便可以使用m_pJSCallbackFunObj作为NPN_InvokeDefault的参数进行回调js的函数了,封装了一下调用过程为cplug的成员函数,可以在需要回调的时候调用
- bool CPlugin::JSCallbackFun(int32_t nState)
- {
- if (m_pJSCallbackFunObj != NULL)
- {
- NPVariant result;
- NPVariant relements[1];
- INT32_TO_NPVARIANT(nState,relements[0]);
- //invoke js fun by js obj
- NPN_InvokeDefault(m_pNPInstance,m_pJSCallbackFunObj,relements,1,&result);
- NPN_ReleaseVariantValue(&result);
- }
- return true;
- }
这段也可以直接放在test函数里面测试回调,nodemo里面增加了一个Test1接测试回调
关于安装:自己提供安装包,让用户一键安装之后各个浏览器都能识别并使用,同时还要顾及到先安装插件后安装浏览器的问题,因此采用的是写注册表的方式,即使后安装浏览器,也能发现
这里碰到的问题记录一下,尽管解决了,可依旧有点懵:
1:在修改runtime的时候,碰到过Fireforx下面可以正常进行调用,但是在chrome下面,about:plugins可以出现,但是调用不了的情况,后来反复折腾,文件对比依旧没有发现哪里出问题,只得小心重来,后面居然好了,Fireforx群里面也有兄弟碰见类似问题,后来也不知道怎么样了,回头再研究下
2:js调用接口传递整数的参数,Fireforx下会存放在NPVariant下的int中,chrome下面会存放在double中,其它的还未测试过,这个问题也是排查了很久,Fireforx下好了之后转到在chrom下面,传递的int全都是0,根本就没有值,后来逐步排查,打印出传递进来的数据类型,才找到数据被放到了doubled段下面,至于原因,依旧困惑,后来索性直接全部改成string
3:编码问题,不知道是不是,刚开始在这方面找了不少原因:
js传递字符串参数时:Fireforx下面一切正常,到chrome下面去,会有乱码现象,找了好久原因,各种编码都尝试过,自己手动强制转也不行,google干嘛呢这是,最后都差点要放弃了,偶然在网上看到个js字符串加上\0的,尝试一下,去,居然好了,在回到Fireforx下面,也好了,依旧不知为什么,无语了。。。。。。
后话:经过一段时间的奋战,初步完工,中间经历了不少困难,中文资料也比较少,官方文档英文一点点的看,遇到个一些的问题在来回折腾反复尝试之后,每每处在就要崩溃的边缘的时候,迎来了柳岸花开。此时先前所有的疲惫烦恼一扫而空,一下子又满怀斗志,继续前行,这种感觉相信做过coder的兄弟们你们都懂的。要不然怎会一头扎进茫茫的code之中呢。。。。。。