在windows平台下,插件就是一个dll,注意到这个dll的def文件内容是:
LIBRARY "" EXPORTS NP_GetEntryPoints @1 NP_Initialize @2 NP_Shutdown @3
插件接口
既然是浏览器调用插件,必然浏览器是通过上面三个接口来调用的。上述三个接口,第三个很明显是结束插件时调用。参考资料:http://colonelpanic.net/2009/03/building-a-firefox-plugin-part-one/
NP_GetEntryPoints – 在插件加载之后立即调用该接口,用于浏览器获取所有可能需要调用的API函数的指针。
NP_Initialize – 为插件提供全局初始化。
NP_Shutdown – 为插件提供全局反初始化。
在np_entry.cpp文件中可以找到上面的几个函数。第一个:
NPError OSCALL NP_GetEntryPoints(NPPluginFuncs* aNPPFuncs) { return fillPluginFunctionTable(aNPPFuncs); }其调用的fillPluginFunctionTable为:
static NPError fillPluginFunctionTable(NPPluginFuncs* aNPPFuncs) { if (!aNPPFuncs) return NPERR_INVALID_FUNCTABLE_ERROR; // Set up the plugin function table that Netscape will use to call us. aNPPFuncs->version = (NP_VERSION_MAJOR << 8) | NP_VERSION_MINOR; aNPPFuncs->newp = NPP_New; aNPPFuncs->destroy = NPP_Destroy; aNPPFuncs->setwindow = NPP_SetWindow; aNPPFuncs->newstream = NPP_NewStream; aNPPFuncs->destroystream = NPP_DestroyStream; aNPPFuncs->asfile = NPP_StreamAsFile; aNPPFuncs->writeready = NPP_WriteReady; aNPPFuncs->write = NPP_Write; aNPPFuncs->print = NPP_Print; aNPPFuncs->event = NPP_HandleEvent; aNPPFuncs->urlnotify = NPP_URLNotify; aNPPFuncs->getvalue = NPP_GetValue; aNPPFuncs->setvalue = NPP_SetValue; return NPERR_NO_ERROR; }fillPluginFunctionTable函数设置了一系列的函数入口,都是很熟悉的在mozilla的开发文档中经常提到的以NPP开头的函数。这些函数是需要在插件中加以实现的。
第二个:
NPError OSCALL NP_Initialize(NPNetscapeFuncs* aNPNFuncs) { NPError rv = fillNetscapeFunctionTable(aNPNFuncs); if (rv != NPERR_NO_ERROR) return rv; return NS_PluginInitialize(); }其调用的fillNetscapeFunctionTable为:
static NPError fillNetscapeFunctionTable(NPNetscapeFuncs* aNPNFuncs) { if (!aNPNFuncs) return NPERR_INVALID_FUNCTABLE_ERROR; if (HIBYTE(aNPNFuncs->version) > NP_VERSION_MAJOR) return NPERR_INCOMPATIBLE_VERSION_ERROR; if (aNPNFuncs->size < sizeof(NPNetscapeFuncs)) return NPERR_INVALID_FUNCTABLE_ERROR; NPNFuncs.size = aNPNFuncs->size; NPNFuncs.version = aNPNFuncs->version; NPNFuncs.geturlnotify = aNPNFuncs->geturlnotify; NPNFuncs.geturl = aNPNFuncs->geturl; NPNFuncs.posturlnotify = aNPNFuncs->posturlnotify; NPNFuncs.posturl = aNPNFuncs->posturl; NPNFuncs.requestread = aNPNFuncs->requestread; NPNFuncs.newstream = aNPNFuncs->newstream; NPNFuncs.write = aNPNFuncs->write; NPNFuncs.destroystream = aNPNFuncs->destroystream; NPNFuncs.status = aNPNFuncs->status; NPNFuncs.uagent = aNPNFuncs->uagent; NPNFuncs.memalloc = aNPNFuncs->memalloc; NPNFuncs.memfree = aNPNFuncs->memfree; NPNFuncs.memflush = aNPNFuncs->memflush; NPNFuncs.reloadplugins = aNPNFuncs->reloadplugins; NPNFuncs.getvalue = aNPNFuncs->getvalue; NPNFuncs.setvalue = aNPNFuncs->setvalue; NPNFuncs.invalidaterect = aNPNFuncs->invalidaterect; NPNFuncs.invalidateregion = aNPNFuncs->invalidateregion; NPNFuncs.forceredraw = aNPNFuncs->forceredraw; return NPERR_NO_ERROR; }这里获取一系列函数的入口,这些函数是浏览器中实现的。
NP_Initialize还调用了一个函数NS_PluginInitialize(),NS_PluginInitialize是我们在plugin.cpp中实现的,随便提一句,NP_Shutdown调用的NS_PluginShutdown也是在plugin.cpp中由我们自己去实现的。
通过对上面的代码的分析,可以发现虽然在def里面只定义了三个接口,但实际上却包括浏览器实现的由插件调用的接口21个以及浏览器要调用的由插件实现的接口13个(实际上NPNetscapeFuncs结构定义了55个函数指针,NPPluginFuncs结构定义了19个函数指针)。换句话说,我们开发插件就是要来实现这13个接口。接口的标准已经由浏览器定义好了,我们怎么去实现以及要实现什么样的功能就全凭我们自己了。
np_entry.cpp这个文件已经分析得差不多了,这个文件包含了两个头文件,"npplat.h"和"pluginbase.h",打开这两个文件来看看里面定义了些什么。npplat.h很简单:
#ifndef npplat_h_ #define npplat_h_ #include "npapi.h" #include "npfunctions.h" #ifdef XP_WIN #include "windows.h" #endif #ifdef XP_UNIX #include <stdio.h> #endif #ifdef XP_MAC #include <Carbon/Carbon.h> #endif #ifndef HIBYTE #define HIBYTE(i) (i >> 8) #endif #ifndef LOBYTE #define LOBYTE(i) (i & 0xff) #endif #endif另外看看pluginbase.h,该文件定义了nsPluginInstanceBase类并声明了四个全局函数: NS_NewPluginInstance(nsPluginCreateData * aCreateDataStruct)
NS_DestroyPluginInstance(nsPluginInstanceBase * aPlugin);
NS_PluginInitialize();
NS_PluginShutdown();
这四个函数都是由我们在plugin.cpp中去实现的,前面的分析中已经知道NS_PluginInitialize()和NS_PluginShutdown()是在np_entry.cpp文件中调用的。
由此观之,我们要开发插件,建立的类首先要继承nsPluginInstanceBase类根据需要实现其中的某些虚函数,另外还需要实现上面这四个全局函数。
接下来研究npn_gate.cpp文件和npp_gate.cpp文件。
NPN函数
打开npn_gate.cpp文件,就可以发现其中实现了20个函数,都是fillNetscapeFunctionTable中的函数,(之前我们发现其中有21个函数,这里为什么只有20个呢?看看NPNetscapeFuncs的定义可以发现,size是个变量不是函数。)而这里的函数名就是以NPN_开头了,按说这是浏览器实现函数不需要在这些地方来实现了呀,这是什么原因呢。来看看其中的函数吧。以一个简单的函数NPN_GetURL为例:其实现代码如下:
NPError NPN_GetURL(NPP instance, const char *url, const char *target) { return (*NPNFuncs.geturl)(instance, url, target); }这个一看咱就明白了,NPN_GetURL确实可以说是浏览器实现的,因为这个函数直接调用了NPNetscapeFuncs的一个函数地址处的函数。
换句话说,在插件实例初始化的时候,将浏览器实现的这些函数的入口地址保存到一个NPNetscapeFuncs结构中,NPN_开头的这些函数名其实是开发插件时需要由开发者定义的,这些函数的实现就直接根据NPNetscapeFuncs结构中的入口地址调用浏览器实现的相关功能。但这些基本都是固定不变的,因此sdk中已经帮我们开发者写好了这些代码,在开发插件时只需要调用NPN_开头的全局函数即可。
NPP函数
npp_gate.cpp文件中实现了13个函数,这13个函数就是NP_GetEntryPoints中fillPluginFunctionTable需要实现的函数。在npp_gate.cpp文件中我们可以发现分别在NPP_New和NPP_Destroy中调用了plugin.cpp中实现的另外两个全局函数NS_NewPluginInstance和NS_DestroyPluginInstance。
这个文件中实现的13个函数基本上都调用了nsPluginInstanceBase类对应的函数,因此这些NPP_开头的函数就相当于是插件开发者实现的。实现这些函数就是要实现nsPluginInstanceBase类的成员函数,可以看到nsPluginInstanceBase的成员函数都是定义为虚函数的,其中 init(NPWindow* aWindow) 、shut()、 isInitialized()三个函数是纯虚函数,在nsPluginInstanceBase类的派生类中必须进行实现,而其他函数就可以根据需要加以实现。
通过上面的一番分析,要写出一个NPAPI的插件,利用这个框架必须至少要做的工作有:
从nsPluginInstanceBase类派生一个类并至少实现其中init() 、shut()、 isInitialized()这三个成员函数。
声明并实现四个全局函数
nsPluginInstanceBase * NS_NewPluginInstance(nsPluginCreateData * aCreateDataStruct);
void NS_DestroyPluginInstance(nsPluginInstanceBase * aPlugin);
NPError NS_PluginInitialize();
void NS_PluginShutdown();
到这里就对插件的接口分析完毕了,开发插件的过程就是实现nsPluginInstanceBase类的部分成员函数以实现需要的功能。要想得心应手的开发插件就必须准确的把握浏览器调用这些NPP函数的机制和流程。
浏览器对插件接口的调用
接下来分析浏览器在何时调用何种接口的问题。首先来理一下nsPluginInstanceBase类的成员函数是如何被NPP_开头的函数所调用的。(注:plugin表示一个nsPluginInstanceBase对象)
NPP接口函数 |
可能调用的nsPluginInstanceBase成员函数或全局函数 |
备注 |
NPP_New |
NS_NewPluginInstance |
创建插件实例 |
NPP_Destroy |
NS_DestroyPluginInstance、plugin->shut |
删除插件实例 |
NPP_SetWindow |
plugin->SetWindow、plugin->isInitialized、plugin->init、NS_DestroyPluginInstance |
窗口创建、移动、改变大小或销毁时调用 |
NPP_NewStream |
plugin->NewStream |
通知插件实例有新的数据流 |
NPP_WriteReady |
plugin->WriteReady |
确定插件是否准备好接收数据(以及其准备接收的最大字节数) |
NPP_Write |
plugin->Write |
调用以将数据读入插件this might be better named “NPP_DataArrived” |
NPP_DestroyStream |
plugin->DestroyStream |
通知插件实例数据流将要关闭或销毁 |
NPP_StreamAsFile |
plugin->StreamAsFile |
为创建流数据提供本地文件名 |
NPP_Print |
plugin->Print |
为嵌入或全屏插件请求平台特定的打印操作 |
NPP_URLNotify |
plugin->URLNotify |
通知插件已完成URL请求 |
NPP_GetValue |
plugin->GetValue |
调用以查询插件信息(还用来获取NPObject/Scriptable 插件的实例) |
NPP_SetValue |
plugin->SetValue |
这是用来为浏览器提供插件变量信息的 |
NPP_HandleEvent |
plugin->HandleEvent |
事件处理函数,对windowed的插件只在MAC操作系统上可用,对于winless的插件所有平台都可用 |
下面这段文字译自:http://colonelpanic.net/2009/05/building-a-firefox-plugin-part-two/
当你明确了插件的生命周期之后会发现它事实上非常简单。初始化入口 NP_Initialize和NP_GetEntryPoints的调用顺序不确定(根据相关文档);但是在实际中,Windows平台上貌似NP_GetEntryPoints 先被调用。记住这一点,下面就是Windows平台上基本的windowed插件初始化的调用顺序:
1. NP_GetEntryPoints – 插件用NPP_New, NPP_Destroy, NPP_SetWindow等函数的入口地址填充一个函数表。
2. NP_Initialize – 插件存储一个NPN_CreateObject, NPN_MemAlloc,等函数入口地址组成的函数表的拷贝。
3. NPP_New – 插件创建一个新的插件实例并初始化
4. NPP_SetWindow – 每个实例都会多次调用这个函数——每次实例窗口创建、改变大小或者其他变化都会调用。
5. NPP_GetValue (Variable = NPPVpluginScriptableNPObject) – 插件创建一个支持脚本的NPObject并返回其指针(调用NPN_RetainObject)。
6. — 标准的插件活动 —
7. NPP_Destroy – 销毁插件实例
8. NP_Shutdown – 销毁所有遗留的插件资源