NPAPI插件开发记录(二)--------npapi与脚本交互(转)

时间:2022-01-29 17:45:53

文章来源:【原创】我的Firefox插件开发之旅(7)——为插件添加和脚本交互的能力

先说一句题外话,上一节一开始我提到了由于.rc导致我自己写的插件不能被FF识别的问题,今天终于找到答案了。在这篇文章中:https://developer.mozilla.org/en/Gecko_Plugin_API_Reference/Plug-in_Development_Overview,有这么一段话:In your development environment, make sure your language is set to "US English" and the character set to "Windows Multilingual." The resource code for this language and character set combination is 040904E4. 看来这FF还只能使用英文。

OK,步入正题。这一小节我来简单说一下如何为插件添加和脚本语言(如Javascript)交互的能力。我会为插件添加几个函数,供Javascript调用。

前面我们提到过浏览器调用插件的方法的顺序,基本上为:NP_GetEntryPoints、NP_Initialize、NPP_New、NPP_SetWindow、NPP_GetValue。在NPP_New中,我们需要创建插件对象的实例,NPP_SetWindow中,浏览器会传入插件窗口的信息,最后一个NPP_GetValue,是浏览器来获取一些插件信息的。NPP_GetValue函数的结构是这样的:

NPError  NPP_GetValue(NPP instance, NPPVariable variable, void *value);

  • instance包含着插件对象实例;
  • variable表示浏览器要获取的信息的类型;
  • value表示返回给浏览器的值

浏览器会传入NPPVpluginScriptableNPObject(作为variable参数)来查询插件是否支持Scriptable功能(即和脚本语言交互的功能),在这里,我们可以利用NPN_CreateObject方法来创建一个NPObject对象,并且作为value返回给浏览器。这样,浏览器就通过这个NPObject对象和我们的插件建立了连接。当页面上Javascript调用了我们插件对象的某个方法时,浏览器会调用该NPObject对象的HasMethod方法来查询是否支持这个方法,如果支持,则会调用NPObject对象的Invoke方法,传入方法名、参数等信息。这样,我们就可以让网页上的脚本语言来执行我们编写的函数了。在Windows上,我们编写的函数就如同编写普通的应用程序一样,可以使用很多Windows API来完成许多复杂的工作。

上面有个问题:如何创建我们自己的NPObject对象?NPN_CreateObject方法如何使用?好在Mozilla给我们提供了npruntime这个例子程序,可以让我们得以参考。

先来看看NPN_CreateObject方法的定义:

NPObject *NPN_CreateObject(NPP npp, NPClass *aClass);

关键在第二个参数上,我们需要提供一个NPClass指针。npruntime例子程序中是这么做的:

定义了一个宏DECLARE_NPOBJECT_CLASS_WITH_BASE,其作用就是定义了一个静态的NPClass对象,并且NPClass要求的所有基础方法,都由一个ScriptablePluginObjectBase类来提供。我们根据需要,来创建不同的继承于ScriptablePluginObjectBase的类(比如支持方法的类和支持属性的类),传给DECLARE_NPOBJECT_CLASS_WITH_BASE宏,这样,当浏览器管我们“要”的时候,我们就可以按照它的需要“给”它对应的对象。

npruntime例子中,ScriptablePluginObject是用来处理方法的,而ConstructablePluginObject是用来处理属性的。

 

如何定义一个方法(或属性)?

1、添加一个方法(或属性)很简单,先定义一个静态NPIdentifier类型的变量,例如:

static NPIdentifier s_idSetArgs;

2、在插件对象构造函数中,使用NPN_GetStringIdentifier方法来设置该方法的名称,例如:

s_idSetArgs = NPN_GetStringIdentifier("SetArgs");

其中,SetArgs就是我们提供给脚本语言调用的方法名称。

3、在ScriptablePluginObject的HasMethod方法中,判断传入的方法名:

bool ScriptablePluginObject::HasMethod(NPIdentifier name)
{
    if(name == s_idSetArgs)
    {
        printf("method name = SetArgs\n");
        return true;
    }

    return false;
}

4、在ScriptablePluginObject的Invoke方法中,判断如果传入的方法名称等于我们定义的方法名,则做你想要做得事情:

//////////////////////////////////////////////////////////////////////////
///
/// @brief    如果某个方法支持(使用HasMethod检测),当页面上Javascript代码调用该方法时,会执行本函数
///
/// @param [in] name    方法名
/// @param [in] args    参数值(数组)
/// @param [in] argCount    参数个数
/// @param [in] result    执行后返回给调用者的结果
///
/// @return PR_TRUE表示执行成功,PR_FALSE表示失败
///
//////////////////////////////////////////////////////////////////////////

bool ScriptablePluginObject::Invoke(NPIdentifier name, const NPVariant *args, uint32_t argCount, NPVariant *result)

{
    if(name == s_idSetArgs)

    {

        这里做你想要做得事情

        return PR_TRUE;

    }

    return PR_FALSE;

}

关于方法参数的接收,这里举个例子。比如网页上这么调用:

embedobj.SetArgs("name", "value");

在我们的方法中,就可以这么接收:

if(args != NULL && argCount >= 2)
{
    NPVariant npvName = args[0]; //第一个参数
    NPVariant npvValue = args[1]; //第二个参数
    if(NPVARIANT_IS_STRING(npvName) && NPVARIANT_IS_STRING(npvValue))  //如果两者都是字符串类型(当然你还可以判断是否是其他类型)
    {
        NPString npsName = NPVARIANT_TO_STRING(npvName); //转成NPString
        NPString npsValue = NPVARIANT_TO_STRING(npvValue);

        if(npsName.utf8characters && strlen(npsName.utf8characters) > 0) //限定条件,可以根据需要进行修改。这里限定第一个参数内容不能为空
        {
            int nLenName = strlen(npsName.utf8characters) + 1;
            int nLenValue = strlen(npsValue.utf8characters) + 1;

            PARAMPAIR paramPair;
            paramPair.pName = new char[nLenName];
            memset(paramPair.pName, 0, nLenName);
            paramPair.pValue = new char[nLenValue];
            memset(paramPair.pValue, 0, nLenValue);

            strcpy(paramPair.pName, npsName.utf8characters); //将参数内存存储到我们熟悉的C
            strcpy(paramPair.pValue, npsValue.utf8characters);

            m_vecParamPair.push_back(paramPair);
        }
    }
}

上面的代码中,PARAMPAIR就是一个简单的结构体:

typedef struct tagPARAMPAIR
{
    LPTSTR pName;
    LPTSTR pValue;
}PARAMPAIR, *PPARAMPAIR;

m_vecParamPair是一个vector:vector<PARAMPAIR> m_vecParamPair;

顺便说一句,上面只是代码片段,关于内存释放、vector清空等操作,由于不是这里要说的关键部分,所以没有列出。

OK,现在我们的插件已经可以顺利和网页进行交互工作了。