1.插件是什么
插件是一种遵循一定规范的应用程序接口编写出来的程序。插件必须依附于一个宿主程序,为宿主程序提供增强功能。插件的种类有很多,这里主要讨论浏览器插件。
IE下利用OLE和COM技术开发的浏览器插件称为ActiveX控件。一般以.ocx为扩展名。IE浏览器通过OLE标准与ActiveX控件之间进行交互,完成对现有功能的扩充。
IE和OLE技术都是微软出的,所以如果在非IE浏览器下开发插件,就不能使用现有的COM技术标准。NPAPI技术提供了一种新的开发浏览器下插件的标准。浏览器和插件共同遵守这一标准,完成功能和交互。
2.Netscape插件
支持NPAPI标准的浏览器有很多,这里以Firefox为例进行说明,不同的浏览器在具体的实现上可能有所不同。
Netscape插件是遵循NPAPI标准开发出的对浏览器扩展的插件模块。 在firefox下,将插件(dll)放入firefox安装目录下的plugins文件夹中,启动firefox,浏览器会通过插件的MimeType属性识别出该插件。可以在地址栏中输入about:plugins来查看插件是否被firefox正确识别,也可以通过设置注册表的方式来安装该插件,可以创建HKEY_LOCAL_MACHINE\Software\\MozillaPlugins\\@xxx.cn/xxxx,添加Path字符串项保存插件路径。浏览器会自动加载。
3.MimeType
Mimetype,媒体资源类型,一般用于设定某种扩展名的文件用一种应用程序打开的方式。在编写Netscape插件时,在资源文件中设置该插件的MimeType属性值。如:
[html] view plaincopyprint?
- BEGIN
- BLOCK "040904e4"
- BEGIN
- VALUE "CompanyName", "?????"
- VALUE "FileDescription", "TODO: <File description>"
- VALUE "FileVersion", "1.0.0.1"
- VALUE "InternalName", "npplugins.dll"
- VALUE "LegalCopyright", "TODO: (c) <Company name>. All rights reserved."
- VALUE "OriginalFilename", "npplugins.dll"
- VALUE "ProductName", "????"
- VALUE "ProductVersion", "1.0.0.1"
- VALUE "MIMEType", "application/test-plugins"
- END
- END
BEGIN BLOCK "040904e4" BEGIN VALUE "CompanyName", "?????" VALUE "FileDescription", "TODO: <File description>" VALUE "FileVersion", "1.0.0.1" VALUE "InternalName", "npplugins.dll" VALUE "LegalCopyright", "TODO: (c) <Company name>. All rights reserved." VALUE "OriginalFilename", "npplugins.dll" VALUE "ProductName", "????" VALUE "ProductVersion", "1.0.0.1" VALUE "MIMEType", "application/test-plugins" END END
当浏览器启动后,会在默认的plugins文件夹中加载插件,并读取插件的MimeType属性,并保存到浏览器内部。输入about:plugins即可以获取所有当前识别出的插件已经MimeType属性。当浏览器加载页面遇到<Embed ID = ‘plugins1‘ type=‘application/test-plugins‘/>这样的文件内容时,即知道应该调用MimeType属性值为type的插件来打开该文件。就会在浏览器中查找到该插件,并进行加载和初始化。如果不存在该属性的插件,则因为找不到指定类型的插件而无法打开该媒体文件。
微软的IE浏览器下的ActiveX控件遵循了COM标准,所以使用Object标签的CLSID来标识应该调用那个插件来读取该文件,不需要为插件设定MIME 的编码。
4.Netscape插件的生命周期
1.打开firefox浏览器。Firefox主动读取安装目录下的plugins文件夹,读取该文件夹下的插件的信息,比如mimetype,并保存起来,输入about:plugins可以查看
2.载入相关页面。当遇到标签为<Embed ID = ‘plugins1‘ type=‘application/test-plugins‘/>这样的文件内容时,浏览器主动查找加载mimetype相匹配的插件。加载的过程也是初始化调用接口的过程。初始化完毕后,浏览器会调用插件相关接口创建一个插件实例,与该页面相关联。
3..载入另一个新的页面。如果已经有页面载入,则此后的所有重新打开的web页面都将跳过插件初始化这一步骤,不过要创建新的插件实例与新的页面相对应。
4..关闭一个页面。销毁与该页面关联的插件实例,如果是最后一个页面,则执行反初始化插件操作。
5. NPAPI标准的接口说明
NPAPI标准定义在一组包含了数据结构和接口函数的头文件中。在实际的开发中,需要将这些头文件加入到工程中,并对头文件定义的部分接口编写实现。用到的主要四个头文件有npapi.h,npfunction.h,npruntime.h,nptype.h,这四个头文件可以在firefox的源码例子插件中找到,同时还提供了一组C++编写的框架用来更方便的开发Netscape插件。
简单介绍一下三类接口函数
1..前缀NP_是npapi的插件库提供给浏览器的最上层的接口,一般为动态链接库的导出接口,主要有NP_GetEntryPoints、NP_Initialize、NP_GetMIMEDescription、 NP_GetValue、NP_Shutdown的等几个函数,不同平台的接口可能略有不同,但基本功能都是一样的,都是通过接口来初始化、销毁以及认知此动态库.
[plain] view plaincopyprint?
- LIBRARY "npplugins"
- EXPORTS
- ; Explicit exports can go here
- NP_GetEntryPoints @1
- NP_Initialize @2
- NP_Shutdown @3
LIBRARY "npplugins" EXPORTS ; Explicit exports can go here NP_GetEntryPoints @1 NP_Initialize @2 NP_Shutdown @3
2. 前缀NPP_即NP Plugin是插件本身提供给浏览器调用的接口,主要被用来填充NPPluginFuncs的结构体,主要包括:NPP_New、NPP_Destroy、NPP_SetWindow、NPP_GetMIMEDescription 、NPP_NewStream、NPP_DestroyStream、NPP_StreamAsFile、NPP_WriteReady、NPP_Write、NPP_Print、NPP_HandleEvent、NPP_URLNotify、NPP_GetValue、NPP_SetValue等,详细介绍
NPP_Destroy 删除插件的一个运行实例(instance) NPP_DestroyStream 告知插件将要删除一个流数据 NPP_GetValue 供浏览器查询插件的内部信息 NP_GetValue 供浏览器查询插件的内部信息 NPP_HandleEvent 注册关心的事件,当事件发生时浏览会通知插件 NP_Initialize 只调用一次,在插件加载时调用 NPP_New 创建插件的一个实例 NPP_NewStream 通知插件实例出现了新的流数据 NPP_Print 请求嵌入式打印还是全屏打印 NPP_SetValue 插件添加变量信息 NPP_SetWindow 当窗口创建、移动、改变大小或者销毁时通知插件 NP_Shutdown 销毁插件,与NP_Initialize对应 NPP_StreamAsFile 为数据流提供一个本地文件名 NPP_URLNotify 插件要求通知后,当对于某个URL的请求完成后,浏览器通知插件 NPP_Write 插件读取流数据 NPP_WriteReady 调用NPP_Write之前调用,确实插件可以接收多少字节的数据
3.前缀NPN_XXX类接口一般为浏览器引擎提供给plugin调用的接口主要包括NPN_GetURL、NPN_PostURL、NPN_GetValue、NPN_SetValue、NPN_Status等
当浏览器开始加载插件时,首先调用NP_GetEntryPoint函数用于获取NPP_类函数的地址,为后续调用做初始化工作。接着调用NP_Initialize函数,将浏览器提供的NPN_函数地址通知插件。以上两个动态库导出接口完成了插件后续调用和被调用的初始化工作。当所有插件相关页面退出时,浏览器调用NP_Shutdown来完成反初始化。
接下来当firefox加载页面时,浏览器调用插件的NPP_New函数来创建一个插件实例。当页面退出时,浏览器调用NPP_Destroy来销毁插件实例。注意,只有在第一次加载页面时才会初始化调用插件,后续打开的页面不再重新初始化,因为在第一次初始化时浏览器已经获取了函数接口信息,后续的页面只需要调用NPP_New创建新的插件实例与页面对应即可。同理,只有当最后一个页面退出时,才调用插件的反初始化,即NP_Shutdown函数。这时,浏览器会清除该插件的对象实例,释放资源。
6.NPAPI的主要数据结构
1. 结构NPPluginFuncs:包含了NPP_函数地址的结构体。浏览器中定义一个这样的结构体对象,通过NP_GetEntryPoint函数对该结构体对象赋值,将NPP_函数地址通知浏览器,供浏览器后续调用
2. 结构NPNetscapeFuncs:包含了浏览器中定义的NPN_函数的结构体。插件中定义一个这样的结构体指针,浏览器通过NP_Initialize函数赋值给这个结构体指针,为后续插件调用浏览器中的这些接口服务。
3. 结构NPObject:包含NPClass *_class和uint32_t referenceCount. 插件实例对象
4. 结构NPClass: 包含了访问插件的一组方法。包含在NPObject对象中。主要包含的函数有pluginHasMethod :询问插件是否支持某一js方法。pluginHasProperty :询问插件是否具有某一属性 pluginInvoke :当插件支持该方法时,浏览器调用给方法传递参数和获取返回值。
5. 结构NPVariant:带类型定义的数据信息结构
定义为
typedef struct _NPVariant {
NPVariantType type;
union {
bool boolValue;
int32_t intValue;
double doubleValue;
NPString stringValue;
NPObject *objectValue;
} value;
} NPVariant;
对该结构体的访问可以通过以下的标准宏进行:
NPVARIANT_IS_STRING或者NPVARIANT_TO_BOOLEAN,NPVARIANT_TO_INT32,NPVARIANT_TO_DOUBLE,NPVARIANT_TO_STRING,NPVARIANT_TO_OBJECT等。
NPAPI接口同时提供了一些将其他类型转换为NPVariant结构的宏。如INT32_TO_NPVARIANT,STRINGZ_TO_NPVARIANT,OBJECT_TO_NPVARIANT等,可以很方便的进行数据转换。
7.插件与JS的交互
NPAPI标准提供了一套插件与JS的交互机制。
1. javascript调用插件方法: 浏览器首先会调用NPP_GetValue(NPP instance, NPPVariable variable, void* value)取得NPObject对象的地址。Variable参数为NPPVpluginScriptableNPObject。在取得该对象后浏览器就可以调用插件提供的NPClass函数。最主要的函数有下面几个: pluginHasMethod :询问插件是否支持某一js方法。 pluginHasProperty :询问插件是否具有某一属性 pluginInvoke : 当插件支持某一方法时,浏览器将会调用该函数执行插件为js提供的这一方法。那么对于提供的很多方法插件如何在该函数内区分。分析这个函数: pluginInvoke(NPObject *obj, NPIdentifier name, const NPVariant *args, uint32_t argCount, NPVariant *result); obj是插件里的NPObject对象地址。 Name表示插件提供方法的名字,通过对比这个参数来区分插件提供的不同方法。 args和argcount分别表示js传来的参数地址和参数个数。 result是函数返回给js的结果。
2. 插件调用js内部的回调函数: Js可以通过2种方式为插件设置回调函数。示例如下: <script language=javascript> Plugin.Onfun = fun;//方式一 通过设置插件属性传入回调函数地址 Plugin.Onfun(fun);//方式二 通过调用插件函数传入回调函数地址 Function fun(){} < /script>
在插件内部,当js函数地址传到插件时,浏览器把它封装为一个NPObject对象,里面存有函数地址 方式一: 在插件内部,浏览器会调用pluginHasproperty确认插件是否有该属性。如果有然后浏览器调用pluginSetproperty函数。pluginSetProperty(NPObject *obj, NPIdentifier name, const NPVariant *variant)的第二个参数判断是哪个属性,第三个参数就是NPObject对象地址。 方式二:在插件内部,浏览器会调用pluginHasmethod确定是否支持该方法。然后调用pluginInvoke,这里面的args参数包含了回调函数NPObject地址。 js设置完回调函数后,插件就可以调用该函数了。需要使用NPN_InvokeDefault,示例代码如下: bool bret = gBrowser->invokeDefault(npp, callbackNPObject, &pV, 1, &result);
另外,插件也可以直接调用js中的函数。在插件内部调用浏览器的getUrl函数。具体格式如下: gBrowser->geturl(inst(), “javascript:function()”, "_self"); 如果想传入整数参数,上面函数第二个参数应写成: “javascript:function(“+num+”)”。 如果传入字符串参数,上面函数第二个参数为: “javascript:function(/’“+”string”+”/’)”。如果字符串含有中文,需要进行url encode。
8.开发Netscape插件的一般方法
尽管开发netscape插件有不少可用框架。但是开发一个典型的netscape插件主要要做的工作有以下:
1.编写一个动态链接库。 插件的表现形式就是一个动态链接库,所以首先编写一个dll文件或者.so文件供浏览器调用。注意,插件的名称应该以np开头,NPAPI标准的默认规则。
2.添加dll导出接口。 在xx.def文件中添加dll的导出函数,如图
上面导出了三个NP_函数,供浏览器调用。这三个函数的作用前面已经说过,不再细述
3.添加插件的mimetype。首先为插件添加一个.rc文件,注意该资源文件的默认语言应该是英文,简体中文的话浏览器识别不出,然后在.rc文件中添加mimetype属性。
如图
4.实现npapi.h中声明的NPP函数。
5.在Np_GetEntryPoints中将NPP_函数地址赋值给传出参数。
6..在NP_Initialize中保存传入的NPNetscapeFuncs对象指针,供插件后续调用
7.在Np_Shutdown中处理反初始化操作。
8.对NPClass中的函数进行实现