NPAPI插件与JS交互开发详细记录

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

那么浏览器是如何完成将插件转换为JS能够识别的对象的呢?我们发现,在NPP_GetValue的实现中有:
if (variable==NPPVpluginScriptableNPObject)
{
(NPObject *)value = plugin->GetScriptableObject();
}

也就是说,浏览器会调用NPP_GetValue (instance, NPPVpluginScriptableNPObject, value)并将来获取插件的scriptable对象。进一步看看plugin是如何获取scriptable对象的:

NPObject* CPlugin::GetScriptableObject()
{
    if (!m_pScriptableObject) {
       m_pScriptableObject = NPN_CreateObject(m_pNPInstance, &CScriptObject::nsScriptObjectClass);
    }

    if (m_pScriptableObject) {
       NPN_RetainObject(m_pScriptableObject);
    }

    return m_pScriptableObject;
}

对象存在时用NPN_RetainObject来获取对象,对象不存在时用NPN_CreateObject来创建一个对象。
当我们在JS中设置/获取属性或者调用方法时,都会在这个scriptable对象中操作,在使用结束时(CPlugin的析构函数中)使用NPN_ReleaseObject(m_pScriptableObject);来释放这个对象。
简单解释一下对象是如何创建的(一般情况下我们可以不知道,只需要按照demo中的代码使用就可以了,如果只想知道如何实现与JS的交互请跳至下一部分),看看MDN上相关说明:
NPObject *NPN_CreateObject(NPP npp, NPClass *aClass);
The function has the following parameters:
npp
The NPP indicating which plugin wants to instantiate the object.
aClass
The class to instantiate an object of.
第一个参数很好搞定,第二个参数比较费解,创建时传入的&CScriptObject::nsScriptObjectClass实际上是基类nsScriptObjectBase的NPClass变量,结合说明可以知道,NPN_CreateObject是根据所传入的NPClass类创建一个NPObject并返回这个对象的指针。NPN_CreateObject中调用NPClass类的NPAllocateFunctionPtr成员来为NPObject分配内存,看到NPClass的NPAllocateFunctionPtr成员是nsScriptObjectBase::_Allocate函数,该函数则是调用nsScriptObjectBase::AllocateScriptPluginObject来实现的,AllocateScriptPluginObject的实现在Plugincpp中,可以看到其实现代码就是return (NPObject*)new CScriptObject(npp);也就是创建一个新的CScriptObject对象,这里绕过来绕过去这么复杂,其实就是要做这样一件事情:我们设计scriptableobject类时会新建一个类,而基类nsScriptObjectBase却需要用我们设计的scriptableobject类的构造函数来分配内存并转换成NPObject,最终由NPP_GetValue返回给浏览器,JS实际上就是与浏览器获取到的这个对象来交互的。
仔细研究过npruntime代码的人可能会发现,npruntime中有一个很晦涩的宏DECLARE_NPOBJECT_CLASS_WITH_BASE及GET_NPOBJECT_CLASS,当然从名称可以知道是用基类声明一个变量,并用GET_NPOBJECT_CLASS来引用这个变量,这就相当于是我在基类中定义的nsScriptObjectClass。
实现一个scriptable对象的类其实并不难,只需要从NPObject派生一个类并逐一实现NPClass中的几个函数指针所需要的函数。这里搞得如此复杂就是为了能够设计一个基类,并一劳永逸的不再修改这个基类。本章最后一个示例会给出实现一个最简单的scriptable对象的例子。
属性

在JS中一个对象具有的属性可以比较灵活的设置,比如一个对象obj本来不具有属性kit,调用obj.kit会是undefined,然而当我们设置obj.kit=some_val之后,再次调用obj.kit就会有相应的属性了。
另一方面,在实现插件dll的代码中(后文称为C++代码中),插件对象是一个派生自NPObject的对象,我们也可以很方便的为其设置成员变量,要在插件中实现与JS交互,那么就需要C++代码中的属性(变量)与JS中属性能够互相访问。
可以进行交互的属性分为一般属性及只读属性,只读属性是对于JS来说的,毕竟插件中的代码相对于JS来说是更加底层的,可以不允许JS修改C++中保持的变量,但若想要防止C++更改JS中的变量值却是比较不现实的。
从NPObject的定义可以看到,NPObject包括一个指向NPClass对象的指针和一个引用计数器。NPClass则由诸如hasProperty、hasMethod等函数指针。要实现一个可以与JS交互的插件,就需要实现hasProperty、hasMethod等接口。前文我们知道浏览器调用NPN_CreateObject创建scriptable对象,这里介绍我们在scriptable对象中实现可交互属性。
大概过程是这样的:浏览器在获取到scriptable对象之后,就会调用对象的hasProperty、hasMethod来判断该对象是否具有某个属性或方法,当JS中访问属性或调用函数是就会调用scriptable对象的getProperty、involve等函数来获取属性值或者执行函数。
要设置一个属性(这里以foo为例),首先需要定义一个NPIdentifier来方便保存属性的标识,一般的插件中是设置为全局static变量,我将其设置为CScriptObject类的static成员变量,不设置为全局的,如下:
static NPIdentifier foo_id;

类的中声明的static变量并不会初始化,还需要在cpp文件中,对其初始化:
NPIdentifier CScriptObject::foo_id;

另外我设置一个变量来保存属性值,这个变量要CScriptObject类可以访问,或者通过某个函数访问,简便起见直接设置为CScriptObject类的私有成员变量:
int m_vfoo;

接下来在适当的位置对这个id与要设置的属性关联起来,我选择在CPlugin类的构造函数中执行:
CScriptObject::foo_id = NPN_GetStringIdentifier(“foo”);

如前所述,浏览器会调用CScriptObject类的HasProperty来判断是否具有某属性,那么我们在HasProperty中如下实现:
bool CScriptObject::HasProperty(NPIdentifier name)
{
return name == foo_id;
}

在JS中可以设置属性,需要实现SetProperty
bool CScriptObject::SetProperty(NPIdentifier name, const NPVariant *value)
{
if(name==foo_id){
if(value->type == NPVariantType_Int32)
m_vfoo = NPVARIANT_TO_INT32(*value);
return true;
}
}

     最后在GetProperty中返回属性值:

bool CScriptObject::GetProperty(NPIdentifier name, NPVariant *result)
{
if (name == foo_id)
{
INT32_TO_NPVARIANT(m_vfoo,*result);
m_vfoo++;
return true;
}
}

     为了与JS中设置属性值进行区别,每次获取之后,把属性的值+1可以在JS中多次获取该属性值,发现每次获取的值都会增加一个,说明确实是获取到了插件中设置的属性。
      在JS中设置/获取foo属性,HTML中相应代码为:
     property test:
    int property<input id = "fooinput" value="0"></input>
    <button onclick = "btnsetfoo();">set FOO</button>
    <button onclick = "btngetfoo();">get FOO</button><br />

     JS代码(片段)如下:
function btnsetfoo()
{
    var val = document.getElementById("fooinput");
    obj.foo = parseInt(val.value) ;
}
function btngetfoo()
{
    alert(obj.foo);

}

    如果想实现只读属性,不实现SetProperty即可。

供JS调用的插件接口

    实现可以在JS中调用的接口,过程与属性相似,这里以实现一个函数func为例,首先在CScriptObject类中声明一个标识:

static NPIdentifier func_id;
初始化:
NPIdentifier CScriptObject::func_id;
接下来将这个id与要设置的函数名关联起来:
CScriptObject::func_id = NPN_GetStringIdentifier(“func”);
浏览器会调用CScriptObject类的HasMethod来判断是否具有某个函数,那么我们在HasMethod中如下实现:
bool CScriptObject::HasMethod(NPIdentifier name)
{
return name == func_id;
}
JS中调用obj.func()时,会执行到Invoke,其中我们利用messagebox弹出一个消息框,实现如下:

    ```

bool CScriptObject::Invoke(NPIdentifier name, const NPVariant *args, uint32_t argCount, NPVariant *result)
{
if (name == func_id)
{
MessageBox(NULL,_T(“func”),_T(“”),0);
return true;
}
return false;
}

     在JS中调用func函数,HTML中相应代码为:

FUNC
JS代码(片段)如下:
function btnclick()
{
obj.func();
}
供插件调用的JS函数(JS callback)

可以将JS函数作为回调供插件调用,假设我们需要插件调用JS函数如下:
function objJSfunc()
{
alert(“JS function called!”);
}

在JS中,函数其实也是一个object,那么如何将这个object传递给插件,并在插件中执行呢?我们可以将这个object作为插件的一个属性,在执行的时候利用NPN_InvokeDefault来执行,以下是完整过程:
首先需要一个变量来保存这个JS函数或者函数的标识,JS函数作为一个对象,因此可以将其保存为一个NPObject对象,可以用全局变量也可以将其作为某个类的成员变量,我将这个NPObject作为CPlugin类的一个成员,声明成员 变量:
NPObject* m_jsObj;
在构造函数中初始化为NULL:
m_jsObj = NULL;
使用完毕之后需要释放这个对象,我们在析构函数中执行:
if (m_jsObj!=NULL)
NPN_ReleaseObject(m_jsObj);
接下来,将JS函数作为一个属性,与前文设置一般属性是一样的,先声明一个标识:

static NPIdentifier jsfunc_id;

初始化:
NPIdentifier CScriptObject::jsfunc_id;
与要设置的属性名称关联起来:
CScriptObject::jsfunc_id = NPN_GetStringIdentifier(“OnJsFunc”);
接下来在HasProperty中:

bool CScriptObject::HasProperty(NPIdentifier name)
{
    return name == jsfunc_id;
}
      然后SetProperty:
bool CScriptObject::SetProperty(NPIdentifier name, const NPVariant *value)
{
    if (name == jsfunc_id)
    {
        CPlugin * plugin = (CPlugin*) m_npp->pdata;
        if (plugin->m_jsObj == NULL)
        {
            plugin->m_jsObj = NPN_RetainObject(NPVARIANT_TO_OBJECT(*value));
        }
        return true;
    }
}```
         当然这个就不需要实现GetProperty了。这样就实现了JS回调函数的设置,只需要在JS中使用obj.OnJsFunc = objJSfunc;为其设置好需要调用的JS函数就可以了。
         设置好之后,就是在C++中如何调用这个JS函数了,要调用这个函数,就需要访问我们设置的m_jsObj,因此只要能够访问我们设置的这个变量的位置都可以执行这个JS函数,之前我们设置了一个func供JS调用,这里我们假设func执行完毕之后需要调用我们设置的这个JS函数,可以在func的执行代码最后加上调用JS函数的代码,Invoke函数就变为如下形式了:

bool CScriptObject::Invoke(NPIdentifier name, const NPVariant *args, uint32_t argCount, NPVariant *result)
{
if (name == func_id)
{
MessageBox(NULL,_T(“func”),_T(“”),0);
CPlugin* plugin = (CPlugin*) m_npp->pdata;
if (!(!plugin->m_jsObj))
{
NPVariant result;
NPN_InvokeDefault(m_npp,plugin->m_jsObj,NULL,0,&result);
NPN_ReleaseVariantValue(&result);
}
return true;
}
return false;
}
“`
以上就是设置JS回调的完整过程,与JS交互有关的话题可能还包括编码的转换,在遇到中文时处理不好可能会导致乱码,只要记住一个原则就是JS处理的字符都是UTF-8编码的,而C++中的字符可能是unicode的也可能是ansi的,因此需要根据实际情况进行编码的转换就可以解决中文乱码的问题。我给出的scriptdemo还包含:str属性可以设置字符串类型的属性,funci2i处理输入为int输出为int的函数,funcs2s处理输入为字符串输出为字符串的函数。目前没有发现有乱码的问题,因此这里就不再对编码转换的话题做过多的介绍了,如果有朋友发现scriptdemo中有乱码的问题,请及时反馈给我,需要的话以后再来补充。
接下来实现一个简单的JS数组对象,可以说是一个简化的scriptable对象的设计。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值