本文详细分析插件的代码是如何执行的,主要分析np_entry.cpp、npn_gate.cpp和npp_gate.cpp.希望能够有所收获。
在windows平台下,插件就是一个dll,注意到这个dll的def文件内容是:
NP_GetEntryPoints
NP_Initialize
NP_Shutdown
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_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_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的插件所有平台都可用 |
可见,NPP接口基本上与nsPluginInstanceBase类的成员函数一一对应。
初始化入口 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 – 销毁所有遗留的插件资源