当前位置:编程学习 > C/C++ >>

自行设计NPAPI开发框架

    经历了一年有余的插件开发,对插件的工作机制也比较熟悉了,在开发插件的过程中使用sdk中的np_entry.cpp、npn_gate.cpp、npp_gate.cpp以及pluginbase.h这几个文件,极大的提高了插件开发的效率,使开发过程变得简单高效,但是在使用的过程中也发现了一些不足之处以及一些细微的bug。在开发过程中我已经对这几个文件进行了不同程度的修改以满足我的开发需求。虽然修改了能满足我的需求,但总有一个重写该框架的想法。前面因为排除一个bug在Firefox源代码中寻找到了一份非常有价值的测试插件实例代码,并进行了研究,结合上述提及的几个sdk中的文件以及为scriptable插件准备的npruntime实例。综合起来写就了一份自己的插件开发框架代码。
    本文以我写这个插件开发框架的过程为基本线索,介绍我是如何来写这个框架的,同时介绍NPAPI插件的工作机制,希望插件开发初学者看完本文之后对NPAPI插件一个清晰的认识;也希望熟悉插件开发的朋友看到此文之后能够对插件有新的感悟,体验到新的东西;更希望所有看到本文之后对插件开发或对本文提出的框架有任何意见和建议的朋友能与我交流。
    本文主要依据windows平台下开发可用于Firefox浏览器的NPAPI插件来阐述,一般来讲对于windows平台其他支持NPAPI插件机制的浏览器也是适用的,并且与其他平台的NPAPI插件开发基本原理也应该是等同的。但本文不保证其他平台或其他浏览器中是完全一样的。下面言归正传。
 
插件的本质
    插件的本质,就是一个供浏览器调用的动态链接库,在windows平台是一个dll文件,在unix平台是so文件。只不过NPAPI插件规范了这个动态库的接口,规定了接口需要实现哪些最基本的功能。既然是动态库,就从定义接口之处对插件进行寻根探源,不管哪个插件代码中相信都可以找到.def文件中如下描述:
EXPORTS
NP_GetEntryPoints @1
NP_Initialize @2
NP_Shutdown @3
可能有的还有另外一个:
NP_GetMIMEDescription @4
    因此有必要看看这几个接口实现了什么功能, NP_GetEntryPoints的代码如下:
[cpp]  
NPError OSCALL NP_GetEntryPoints(NPPluginFuncs* pFuncs)  
{  
    if (!fillPluginFunctionTable(pFuncs)) {  
       return NPERR_INVALID_FUNCTABLE_ERROR;  
    }  
  
    return NPERR_NO_ERROR;  
}  
 
NPError OSCALL NP_GetEntryPoints(NPPluginFuncs* pFuncs)
{
    if (!fillPluginFunctionTable(pFuncs)) {
       return NPERR_INVALID_FUNCTABLE_ERROR;
    }
 
    return NPERR_NO_ERROR;
}
    很简单,所调用的函数名已经说明了这个接口的功能:填充插件函数表。接下来看看NP_Initialize,代码如下:
[cpp]  
NPError OSCALL NP_Initialize(NPNetscapeFuncs* aNPNFuncs)  
{  
    NPError rv = fillNetscapeFunctionTable(aNPNFuncs);  
    if (rv != NPERR_NO_ERROR)  
        return rv;  
  
    return NS_PluginInitialize();  
}  
 
NPError OSCALL NP_Initialize(NPNetscapeFuncs* aNPNFuncs)
{
NPError rv = fillNetscapeFunctionTable(aNPNFuncs);
if (rv != NPERR_NO_ERROR)
return rv;
 
return NS_PluginInitialize();
}
 
    同样的,所调用的函数名已经说明了该接口的功能:填充浏览器端函数表。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函数中很多类似如下的语句:
[cpp] 
pFuncs->newp = NPP_New;  
pFuncs->destroy = NPP_Destroy;  
 
pFuncs->newp = NPP_New;
pFuncs->destroy = NPP_Destroy;
其中pFuncs 就是NPPluginFuncs结构的指针,上面这两条语句就将NPP_New和 NPP_Destroy(这些是需要我们插件开发者来实现的函数)与NPPluginFuncs结构中的newp和destroy联系起来了。浏览器调用NPPluginFuncs结构的newp指针就是在调用我们实现的NPP_New。
          再来看看NPN_函数的实现,以NPN_GetValue为例,代码如下:
[cpp]  
NPError NPN_GetValue(NPP instance, NPNVariable variable, void* value)  
{  
        return sBrowserFuncs->getvalue(instance, variable, value);  
}  
 
NPError NPN_GetValue(NPP instance, NPNVariable variable, void* value)
{
        return sBrowserFuncs->getvalue(instance, variable, value);
}
直接调用了NPNetscapeFuncs结构中的相应函数,因此我们调用NPN_GetValue其实就是在调用NPNetscapeFuncs结构中的getvalue。
        其实将这些NPP_和NPN_的函数结构NPPluginFuncs和NPNetscapeFuncs联系起来的工作都是几乎一样的,于是就有了NPAPI插件开发的各种框架,这些框架的最基本作用就是干这事儿。有了框架我们开发插件就可以集中精力在实现这些NPP开头的函数的功能上。
        以此为指导思想,写出一个插件,只有一个头文件和一个cpp文件(当然还有rc文件和def文件),编译生成dll在Firefox中about:plugins页面可以看到我们的插件。该代码请参考本文提供的附件。我将其命名为aemo,就当是alpha版的demo吧!下载地址:http://download.csdn.net/detail/z6482/4913874
 
面向对象开发插件
         相信绝大多数插件开发者都是选择C++来开发插件的吧,利用C++面向对象对插件开发进行一定的封装,可以让我们在开发插件的过程中更加专注于插件的实际功能。为了达到这个目的,我们最直接的想法就是将前面提到的NPP_开头的函数封装到一个类中,当我们开发插件的时候就只需要根据我们实际的功能需求来实现类中的相应函数就可以了。来看看sdk中是如何做的:
[cpp]  
NPError NPP_NewStream(NPP instance, NPMIMEType type, NPStream* stream, NPBool seekable, uint16_t* stype)  
{  
    if (!instance)  
        return NPERR_INVALID_INSTANCE_ERROR;  
  
    nsPluginInstanceBase * plugin = (nsPluginInstanceBase *)instance->pdata;  
    if (!plugin)   
        return NPERR_GENERIC_ERROR;  
  
    return plugin->NewStream(type, stream, seekable, stype);  
}  
 
NPError NPP_NewStream(NPP in
补充:软件开发 , C++ ,
CopyRight © 2022 站长资源库 编程知识问答 zzzyk.com All Rights Reserved
部分文章来自网络,