关闭

NPAPI插件开发详细记要:插件运行流程分析

1090人阅读 评论(0) 收藏 举报
分类:
NPAPI插件开发详细记录:插件运行流程分析
本文详细分析插件的代码是如何执行的,主要分析np_entry.cpp、npn_gate.cpp和npp_gate.cpp.希望能够有所收获。
在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的插件所有平台都可用

可见,NPP接口基本上与nsPluginInstanceBase类的成员函数一一对应。
下面这段文字译自: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 – 销毁所有遗留的插件资源
0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:178929次
    • 积分:3400
    • 等级:
    • 排名:第9883名
    • 原创:148篇
    • 转载:135篇
    • 译文:3篇
    • 评论:8条