NPAPI 插件运行流程分析

本文详细分析插件的代码是如何执行的,主要分析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函数

打开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的插件所有平台都可用

可见,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 – 销毁所有遗留的插件资源

你可能感兴趣的:(api)