NPAPI开发流程介绍(支持chrome)

来源:互联网 发布:mac口红子弹头 编辑:程序博客网 时间:2024/04/28 04:59


一、   基本原理

项目需要支持chrome浏览器的使用,需求要支持ActiveX控件或COM组件能在chrome下运行,也就是说要进行使用NPAPI的技术进行对控件封装, 使用动态库都是同理。于是研究了NPAPI的开发过程和原理,现做出以下一些记录。

具备条件: NPAPI SDK. 开发环境。(SDK可以官网下载或其他途径获取);

一个基本的结构式如下所示:

插件的本质

插件的本质,就是一个供浏览器调用的动态链接库,在windows平台是一个dll文件,在unix平台是so文件。只不过NPAPI插件规范了这个动态库的接口,规定了接口需要实现哪些最基本的功能。

 

二、   开发流程

开发环境:win7 X64, vs2013,chrome版本 28.0.1500.44.

开发流程简介:

1.        创建工程

创建一个win32 工程,下一步选择dll和空工程。如下图:

Name项一定要以np开头,为了将来适应不同操作系统,最好全小写,不要太长,尽量控制在8字符内。本例定义为nptestdemo ,工程路径自定义即可;

NPAPI的SDK 本例定义为F:\Propram Code Lib\nptestdemo\ffplugin, 如下图;

 

2.        添加文件, 将NPAPI的文件添加到工程中(NPAPI的有几个文件必须添加到工程)

我的NPAPI SDK所在位置如下图所示:

 

将NPAPI SDK中Common的文件添加到工程;

路径为:…\ffplugin\sdk\samples\common\

 

3.        添加def文件
新建一个def文件,命名最好与项目一致。如下图;

编辑nptestdemo.def文件:

文件中加入以下内容:

 

LIBRARY"nptestdemo" 

 

EXPORTS 

NP_GetEntryPoints@1 

NP_Initialize@2 

NP_Shutdown @3 

4.        添加资源

新建一个资源文件:选择Version, 选择新建,会自动生成一个资源文件。

资源文件右键选择“查看代码”,打开资源文件,编辑;

找到如下一段:

BEGIN

    BLOCK"StringFileInfo"

    BEGIN

        BLOCK"040904e4"   //修改BLOCK 一定要是"040904e4"

        BEGIN

            VALUE"CompanyName","TODO: <公司名>"

            VALUE"FileDescription","TODO: <文件说明>"

            VALUE"FileVersion","1.0.0.1"

            VALUE"InternalName","nptestde.dll"

            VALUE"LegalCopyright","Copyright (C) 2017"

 

// MIMEType是plugin的唯一标示,需要自己定义

//通常的格式是"application/“+ [plugin name]
                             //本例中定义为"application/demo-plugin"

            VALUE "MIMEType","application/nptestdemo-plugin" 

 

            VALUE"OriginalFilename","nptestde.dll"

            VALUE"ProductName","TODO: nptestdemo.dll_mkx"

            VALUE"ProductVersion","1.0.0.1"

        END

    END

    BLOCK"VarFileInfo"

    BEGIN

        VALUE"Translation",0x409,1252 //修改VALUE值这两个值

    END

END

5.        添加最关键的部分:Plugin实现类

添加一个C++类: CPlugin.  要继承自nsPluginInstanceBace. (注意包含父类文件)

编辑Plugin.h文件:

重写父类三个虚函数:

#pragma once

#include "pluginbase.h"

 

class CPlugin :public nsPluginInstanceBase

{

public:

   CPlugin(NPP pNPInstance);

   ~CPlugin();

        //重写以下三个虚函数

    NPBoolinit(NPWindow* pNPWindow)

    {

       m_bInitialized = TRUE; return TRUE;

    }

    voidshut() { m_bInitialized = FALSE; }

    NPBoolisInitialized() { return m_bInitialized; }

 

 

private:

    NPPm_pNPInstance;

    NPBoolm_bInitialized;

};

 

编辑Plugin.cpp文件,实现四个全局函数;

 

NPError NS_PluginInitialize()

{

    returnNPERR_NO_ERROR;

}

 

void NS_PluginShutdown()

{

}

 

nsPluginInstanceBase *NS_NewPluginInstance(nsPluginCreateData * aCreateDataStruct)

{

    if(!aCreateDataStruct)

       return NULL;

 

   CPlugin * plugin = new CPlugin(aCreateDataStruct->instance);

    returnplugin;

}

 

voidNS_DestroyPluginInstance(nsPluginInstanceBase * aPlugin)

{

    if(aPlugin)

       delete(CPlugin *)aPlugin;

}



6.        修改项目属性,加入SDK链接

在第5步之前,应该将工程属性修改,C++->常规->附加包含目录中加入sdk的包含目录;本项目按照SDK路径加入路径为:..\ffplugin\sdk\samples\include; ..\ffplugin\base\public

加入添加预编译宏X86

C++->预处理器->预处理器定义中加入 _X86_

这个宏要是没有加,会报出错误     1       error C1189: #error : "No Target Architecture"等错误还有会报很多vs库文件中的类型错误。所以该编译宏必须添加;

 

7.        注册、测试

本例编译后,生成文件为nptestdemo.dll

要向注册表注册该文件,打开注册表,注册路径为:

[HKEY_CURRENT_USER\Software\MozillaPlugins]

在该路径下新建@mozilla.com.cn/demo(该名称可以自定义创建,名称自定义)

新建字符串数据“Path” 值为nptestdemo.dll的绝对路径。本例为:F:\Propram Code Lib\nptestdemo\Debug\nptestdemo.dll.  下图:

 

         打开chrome浏览器(版本为开发环境中说明的版本)在地址栏输入“about:plugins” 。如果在plugin列表中有本例的nptestdemo.dll即说明我们的plugin示例已经成功完成,并且浏览器已经成功加载。(360浏览器极速模式亦可完成此动作)

 

至此,一个简单的流程就完成了。这只是完成了一个基本简单的框架,实际上什么也做不了。浏览器只是加载了插件,实际上相当于和浏览器没发生任何交互动作。

插件的调用

写一个简单的html页面来调用该插件;

1.  <html>  

2.      <head>  

3.          <script>  

4.              window.onready = function(){  

5.    

6.              }  

7.              function toDoSt(){  

8.                  var plugin = document.getElementById("plugin");  

9.                  alert(plugin.version);  

10.             }  

11.         </script>  

12.          //插件以这种方式加载

13.         <embed id="plugin" type="application/x-npTest" >  //这里填入的是插件的     唯一标识,还是的吗就是资源文件中添加的那个,即VALUE "MIMEType","application/nptestdemo-plugin"这个东东;

14.     </head>  

15.     <body>  

16.         <input type="button" onclick="toDoSt()" value="test">  

17.     </body>  

18. </html>  

 

8.         

9.        S

10.    S

11.    S

 

三、   函数介绍

前面说插件就是动态库,就从定义接口之处对插件进行寻根探源,不管哪个插件代码中相信都可以找到.def文件中如下描述:
EXPORTS
NP_GetEntryPoints @1
NP_Initialize @2
NP_Shutdown @3
可能有的还有另外一个:
NP_GetMIMEDescription @4 //这一项可有可无,一般不加这个;

 

SDK中主要文件包括:np_entry.cpp、npn_gate.cpp、npp_gate.cpp、basic.def、pluginbase.h等几个主要文件;

其中有很多接口;

接口说明:

NP_XXX 是npapi的插件库提供给浏览器的最上层的接口,

NPP_XXX即NP Plugin是插件本身提供给浏览器调用的接口, 主要被用来填充NPPluginFuncs的结构体.

NPN_XXX即NP Netscape ,是浏览器提供给插件使用的接口,这些接口一般都在NPNetscapeFuncs结构体中。

1)  NP_XXX类函数

NP_XXX类接口一般为动态链接库的导出接口,对应文件为:np_entry.cpp,主要有NP_Initialize、NP_GetMIMEDescription、 NP_GetEntryPoints、NP_GetValue、NP_Shutdown的等几个函数,不同平台的接口可能略有不同,但基本功能都是一样的,都是通过接口来初始化、销毁以及认知此动态库。

 

有必要看看这几个接口实现了什么功能:

NP_GetEntryPoints的代码如下

  1. NPError OSCALL NP_GetEntryPoints(NPPluginFuncs* pFuncs)  
  2. {  
  3. if (!fillPluginFunctionTable(pFuncs)) {  
  4. return NPERR_INVALID_FUNCTABLE_ERROR;  
  5.     }  
  6. return NPERR_NO_ERROR;  
  7. }

很简单,所调用的函数名已经说明了这个接口的功能:填充插件函数表。

接下来看看NP_Initialize,代码如下:

  1. NPError OSCALL NP_Initialize(NPNetscapeFuncs* aNPNFuncs)  
  2. {  
  3.     NPError rv = fillNetscapeFunctionTable(aNPNFuncs);  
  4. if (rv != NPERR_NO_ERROR)  
  5. return rv;  
  6. return NS_PluginInitialize();  
  7. }  

同样的,所调用的函数名已经说明了该接口的功能:填充浏览器端函数表。

NP_Shutdown这个接口的功能不言自明,结尾的工作由这个接口来做。另外,NP_GetMIMEDescription是浏览器获取该插件所支持的MIME类型描述的接口,可有可无。

另外在NP_Initialize函数中,宏定义

#if defined(XP_UNIX) && !defined(XP_MACOSX)

#endif

包含的部分直接调用了填充插件函数表的函数,说明在UNIX或MAC上NP_Initialize一个函数实现了windows平台NP_Initialize和NP_GetEntryPoints的功能。

 

于是可以推断出,浏览器与插件直接建立关系的一个过程,首先浏览器利用插件端提供的函数填充一个函数指针表(浏览器调用插件端的函数实现我们开发的功能),接着浏览器将浏览器端提供给插件调用的函数填充一个函数指针表,并将这个表告知插件(插件由这个函数指针表来调用浏览器提供的功能)。

fillPluginFunctionTable函数的参数是一个NPPluginFuncs结构的指针,fillNetscapeFunctionTable的参数是一个NPNetscapeFuncs结构的指针,我们开发插件主要是想让浏览器实现我们指定的功能,因此开发插件的主要工作也就集中在了实现用来填充NPPluginFuncs结构的函数的功能上。用来填充NPNetscapeFuncs结构的函数的功能已经由浏览器实现好了,我们可以在插件中使用。为了方便我们调用浏览器端实现的函数,定义了一堆NPN_开头的全局函数供我们使用,为了方便我们清晰的知道要实现哪些函数接口提供给浏览器,定义了一堆NPP_开头的全局函数。开发插件的工作现在就是实现这堆NPP_开头的函数,并且将这些NPP_和NPN_的函数与前面两个结构NPPluginFuncs和NPNetscapeFuncs联系起来。

如何进行联系呢,看看代码就明了:fillPluginFunctionTable函数中很多类似如下的语句:

  1. pFuncs->newp = NPP_New;  
  2. pFuncs->destroy = NPP_Destroy;  
  3. ………其它略

其中pFuncs 就是NPPluginFuncs结构的指针,上面这两条语句就将NPP_New和 NPP_Destroy(这些是需要我们插件开发者来实现的函数)与NPPluginFuncs结构中的newp和destroy联系起来了。浏览器调用NPPluginFuncs结构的newp指针就是在调用我们实现的NPP_New。实际上我们需要实现的就是这些功能函数。

 

2)  NPP_XXX类函数

NPP_XXX类接口一般为提供给浏览器引擎调用的接口,即用于定义浏览器调用插件的方法,对应文件为:npp_gate.cpp, 主要包括:  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_GetValue是一个笔很重要的函数, 因为在这里常见交互的js脚本实例。

 

经过NP_GetEntryPoints的初始化后,当特定事件发生时,浏览器将会调用这些方法,然后需要注意的是该文件引用了plugin.h文件,创建实例。

NPP_New用于创建插件实例,该方法中通过nsPluginInstanceBase* plugin = NS_NewPluginInstance(&ds); 来创建一个实例,NS_NewPluginInstance()是我们在前面Plugin.cpp中实现的一个全局函数,还记得不,就是它,就是用它来创建一个实例的。

NPP_Destroy方法,用于销毁插件实例,刷新页面,关闭页面等操作会触发

该方法会调用CPluginshut方法再delete掉实例。

NPP_SetWindow方法,插件窗口发生任何变化都会调用该方法,window创建时会调用一次,如果初始化失败则delete掉实例然后返回错误。

NPP_GetValue方法,当获取插件有关的一些信息时会触发该方法调用(如获取插件名,插件实例)javascript操作插件对象时,该方法调用CPluginGetScriptableObject方法,需要自己实现,返回一个脚本操作对象(NPObject)在这里返回到CPlugin,添加GetScriptableObject方法并实现。

NPP_HandleEvent方法,处理事件,该方法调用CPluginhandleEvent方法,继续添加实现吧

3)  NPN_XXX类函数

NPN_XXX类接口一般为webkit引擎提供给plugin调用的接口,对应文件npn_gate.cpp,该文件实现了对浏览器的一些操作的函数主要包括:NPN_GetURLNPN_PostURLNPN_RequestReadNPN_NewStreamNPN_WriteNPN_DestroyStreamNPN_StatusNPN_UserAgentNPN_MemAllocNPN_MemFree NPN_MemFlushNPN_ReloadPluginsNPN_GetJavaEnvNPN_GetJavaPeer NPN_GetURLNotifyNPN_PostURLNotifyNPN_GetValueNPN_SetValue NPN_InvalidateRectNPN_InvalidateRegionNPN_ForceRedraw NPN_GetStringIdentifierNPN_GetStringIdentifiers等函数接口。

 

四、   函数调用流程

本部分主要分析NPAPi 插件的函数调用流程。将详细分析插件的代码是如何执行的,主要分析np_entry.cpp、npn_gate.cpp和npp_gate.cpp。

在windows平台下,插件就是一个dll,注意到这个dll的def文件内容是:

NP_GetEntryPoints 
NP_Initialize
NP_Shutdown

这是整个dll的导出接口,也是调用的入口函数。

NP_GetEntryPoints  – 在插件加载之后立即调用该接口,用于浏览器获取所有可能需要调用的API函数的指针。
NP_Initialize – 为插件提供全局初始化。
NP_Shutdown – 为插件提供全局反初始化。

1)    NP函数

这三个函数的定义是在np_entry.cpp文件中的:

NPError OSCALL NP_GetEntryPoints(NPPluginFuncs* aNPPFuncs)

{

    returnfillPluginFunctionTable(aNPPFuncs);

}

其中fillPluginFunctionTable为:

 

static NPError fillPluginFunctionTable(NPPluginFuncs* aNPPFuncs)

{

  if (!aNPPFuncs)

    returnNPERR_INVALID_FUNCTABLE_ERROR;

  //这些函数是需要在插件中加以实现的。

  // Set up the plugin functiontable 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;

}

其中aNPPFuncs 是全局变量类型为:NPNetscapeFuncsNPNFuncs;

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",打开这两个文件来看看里面定义了些什么。

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文件。

 

2)    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_开头的全局函数即可。

3)    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函数的机制和流程。

4)     浏览器对插件接口的调用

接下来分析浏览器在何时调用何种接口的问题。
首先来理一下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类的成员函数一一对应。

5)    函数调用顺序流程

初始化入口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. — 标准的插件活动—

创建脚本之后会生成一个staticNPClass objectClass;对象,这里会实现对应的方法, 这个对象里面会有页面点击等的响应处理, 在invoke中调用需要封装的dll的接口。(脚本创建等方法在下节说明)
7. NPP_Destroy – 销毁插件实例
8. NP_Shutdown – 销毁所有遗留的插件资源

大概就是这么一个顺序了。

五、   注册调用

Plugin的注册需要写注册表,路径为:

[HKEY_CURRENT_USER\Software\MozillaPlugins\@mozilla.com.cn/demo]

"Path"="E:\\work_space\\timevalePdf_3.0\\out\\debug\\npplugin.dll"

其中:@mozilla.com.cn/demo是新建项,需要自己定义,即新建一个项,名字自定义,然后新建一个字符串值Path, 值为上面的dll绝对路径。

这样就将plugin注册了。然后打开chrom,网址栏输入:about:plugins。插件中出现插件名称,说明chrome已经加载了插件。然后在chrome 打开调用的页面即可调用插件。

如果插件没能加载,则可能是因为浏览器阻止了插件,在网址栏输入chrome://flags/#enable-npapi,选择npapi 启用。具体情况根据浏览器版本不同而不同。

 

六、   脚本化接口

封装一个脚本操作对象

新建一个C++类,该示例命名为PluginObject,继承NPObject我们需要添加静态方法,用于创建该脚本操作的对象。

 

class CPluginObject  :  publicNPObject

{

public:

   CPluginObject(NPP npp);

   ~CPluginObject();

 

public:

   static NPObject* Allocate(NPP npp, NPClass* aClass);

   static void Deallocate(NPObject *npobj);

   static void Invalidate(NPObject *npobj);

   static bool HasMethod(NPObject* obj, NPIdentifier methodName);

   static bool InvokeDefault(NPObject *obj, const NPVariant *args, uint32_targCount, NPVariant *result);

   static bool Invoke(NPObject* obj, NPIdentifier methodName, constNPVariant *args, uint32_t argCount, NPVariant *result);

   static bool HasProperty(NPObject *obj, NPIdentifier propertyName);

   static bool GetProperty(NPObject *obj, NPIdentifier propertyName,NPVariant *result);

   static bool SetProperty(NPObject *npobj, NPIdentifier name, constNPVariant *value);

   static bool RemoveProperty(NPObject *npobj, NPIdentifier name);

   static bool Enumerate(NPObject *npobj, NPIdentifier **identifier,uint32_t *count);

   static bool Construct(NPObject *npobj, const NPVariant *args, uint32_targCount, NPVariant *result);

 

   static NPClass  objectClass;

 

private:

   //函数接口

   NPIdentifier id_func;

   NPIdentifier methodId[NUM_METHODS];  //方法数组

   NPIdentifier propertyId[NUM_PROPERTIES]; //属性

   

   NPP m_npp;

 

   TGEsignProMedicalCtrlLib::ITGEsignProMedicalPlugInPtr m_plugin;

};

将以上声明的static NPClass  objectClass,对象在类外初始化,就是用类中声明的那些方法填充对objectClass对象的成员。

NPClass CPluginObject::objectClass =

{

   NP_CLASS_STRUCT_VERSION,

   CPluginObject::Allocate,

   CPluginObject::Deallocate,

   CPluginObject::Invalidate,

   CPluginObject::HasMethod,

   CPluginObject::Invoke,

   CPluginObject::InvokeDefault,

   CPluginObject::HasProperty,

   CPluginObject::GetProperty,

   CPluginObject::SetProperty,

   CPluginObject::RemoveProperty,

   CPluginObject::Enumerate,

   CPluginObject::Construct

};

没错,这个对象就是我们脚本类交互的重点,他会依次调用这里面的方法。

然后我们回到CPlugin中,这个类中我们实现了一个GetScriptableObjec方法,该方法中通过NPNCreateObject方法创建该对象。

1.  NPObject* CPlugin::GetScriptableObject(){  

2.      return NPN_CreateObject(this->instance,&objectClass);  

3.  }  

this->instance在构造函数时获取并保存下来的NPP对象.

NPN_CreateObject会在浏览器中做一些操作然后回来调用objectClass中的_allocate方法,需要实现该静态方法,new一个PluginObject对象。

1.  NPObject* PluginObject:: Allocate (NPP npp,NPClass* aClass){  

2.      return new PluginObject(npp);  

3.  }  

后面的操作中,浏览器调用了NPNFunc中以上的一些方法则会来调用这些静态方法,并将_allocate返回的值NPObject*指针作为参数传到其他函数中,作为入参,标识该对象

接下来的实现就是按照我们的业务流程来实现了,可以直接在这些静态方法中实现想要的效果,也可以在PluginObject中创建对应的成员函数实现,然后在静态方法中通过nobj参数转换为(PluginObject)类型调用相应成员函数,建议使用这种方式。

例如在构造函数中:

//npp实例强制转换成TGPlugin的对象,就可以访问TGPlugin的成员

CTGPlugin* pPlugin = (CTGPlugin*)m_npp->pdata;

if (nullptr != pPlugin)

 {

        m_plugin =pPlugin->GetActiveXPlugin();

}
我们注意到:在CPluginObject类中定义了NPIdentifier id_func成员,没有错这就是供js脚本调用的类函数接口的一个东西,这个成员在构造中初始化:

id_func = NPN_GetStringIdentifier("func");

初始化后,id_func指向一个需要封装的或调用的dll中的函数接口: func

我们可以在js中这样调用:var Res =plugin.func (参数);//func就是初始化的那个了。

 

其中几个函数比较重要:

HasMethod判断参见是否有该函数,hasMethod等方法的类似于参数methodName都是以identifier作为判断的,可以调用NPN_GetStringIdentifier获取。

HasProperty判断是否有某些属性, getProperty则是获取属性。

invoke调用相应方法,invoke调用dll中的实际的接口,该接口原型为:

boolInvoke(NPObject* obj, NPIdentifier methodName, const NPVariant *args, uint32_targCount, NPVariant *result)

参数说明:

NPObject*obj Allocate返回的对象指针 //可以将其转换成PluginObject对象使用。

NPIdentifiermethodName 为此次调用的方法名。

NPVariant*args 为一个数组指针  //通过他可以获取对象位置参数的值,下标从0开始。

uint32_targCount 为本次调用接口中的参数个数;

NPVariant*result 为接口调用后的返回值。可以返回到上层,被前面js调用中的var Res接受。

注:这里的函数调用也有顺序, 调用发生时,首先会调用HasMethod和HasProperty来检测是否有该方法和某属性,如果有才会调用Invoke,否则Invoke函数不会被调用。

这两个方法如果只是单纯的返回true,不足以说明什么,虽然返回成功, 不一定说明函数调用成功,也就是说它必须有函数体执行检测方法是否存在,并检测是否有某属性,否则Invoke并不会调用。

 

invokeDefault可以在invoke中调用NPN_InvokeDefault来访问,最好不要直接调用,(API,原因未知,一般浏览器都要做进一步操作)

七、   与JS交互

插件对象  

JS中加载一个插件对象可以用下面的方法:

<embed name="npPlugin"type="application/nptgplugin-plugin" width="300"height="500"></embed>

然后通过使用 document.getElementsByTagName 或者 document.getElementById来获取页面中已经存在的插件对象,还可以在JS中使用document.createElement("object");来动态创建对象,并为该对象设置type属性,接着将创建的这个对象添加到页面中,这样就动态创建了一个插件对象。如下JS函数可以根据传入的mimetype创建一个插件对象(chrome、firefox测试有效,其他未测试):

如通过objname来获取:

function getPluginObjcet(objname) {

            if(!isIEBrowser()) {

               if (document.embeds && document.embeds[objname])

                   return document.embeds[objname];

            }

            else{

               return document.getElementById(objname);

            }

        }

这部分在第二部分的插件的调用中已经做了说明和注释。

然后就可以在js中调用插件了,如下:

 

function func()

{

Var Res =plugin.Init(参数);

}

建立交互关系

那么浏览器是如何完成将插件转换为JS能够识别的对象的呢?我们发现,在NPP_GetValue的实现中有:

  1. if (variable==NPPVpluginScriptableNPObject)  
  2. {  
  3.     *(NPObject **)value = plugin->GetScriptableObject();  
  4. }  

没有错这里创建了一个脚本类的对象,前面也已经说过。

也就是说,浏览器会调用NPP_GetValue(instance,NPPVpluginScriptableNPObject, value)并将来获取插件的scriptable对象。进一步看看plugin是如何获取scriptable对象的:前面已经脚本化接口部分说过,通过NPN_CreateObject来创建对象。

当我们在JS中设置/获取属性或者调用方法时,都会在这个scriptable对象中操作,在使用结束时(CPlugin的析构函数中)使用NPN_ReleaseObject(m_pScriptableObject);来释放这个对象。注:发现这样释放对象,接口出现奔溃,原因未知。

 

可以使用一个宏定义来创建对象:DECLARE_NPOBJECT_CLASS_WITH_BASE及GET_NPOBJECT_CLASS

#defineDECLARE_NPOBJECT_CLASS_WITH_BASE(_class, ctor) \

static NPClasss##_class##_NPClass = {                                        \

  NP_CLASS_STRUCT_VERSION_CTOR, \

  Ctor,\

  ScriptablePluginObjectBase::_Deallocate,  \

  ScriptablePluginObjectBase::_Invalidate, \

  ScriptablePluginObjectBase::_HasMethod,  \

  ScriptablePluginObjectBase::_Invoke,   \

  ScriptablePluginObjectBase::_InvokeDefault, \

 ScriptablePluginObjectBase::_HasProperty,  \

 ScriptablePluginObjectBase::_GetProperty,  \

  ScriptablePluginObjectBase::_SetProperty,  \

  ScriptablePluginObjectBase::_RemoveProperty,\

  ScriptablePluginObjectBase::_Enumerate, \

  ScriptablePluginObjectBase::_Construct  \

}

 

#defineGET_NPOBJECT_CLASS(_class) &s##_class##_NPClass

 

当然从名称可以知道是用基类声明一个变量,并用GET_NPOBJECT_CLASS来引用这个变量,这就相当于是我在基类中定义的nsScriptObjectClass。

  实现一个scriptable对象的类其实并不难,只需要从NPObject派生一个类并逐一实现NPClass中的几个函数指针所需要的函数。这里搞得如此复杂就是为了能够设计一个基类,并一劳永逸的不再修改这个基类。本章最后一个示例会给出实现一个最简单的scriptable对象的例子。

八、   调试

1.   调试方法

前面说了加载插件的方法,和注册方法,等这些都做好了的话, 就可以对插件进行调试,插件运行起来后有他自己的进程, 本文档中到的插件件进程是:chrome大进程底下的动态库为nptestdemo.dll调用的一个子进程,确定该进程PID,然后就可以进行调试。

将该进程附加到vs进程即可。

然后就可以像调试ocx或者动态库一样的方式调试插件了;

2.   调试注意事项

注:务必确定加载进程是不是当前进程,并且,代码务必编译过,否则无法命中断点。

 

九、   注意事项

NPAPI文档讲解连接: https://wiki.mozilla.org/NPAPI#NPAPI_SDK


原创粉丝点击