通过前一篇博客,已经实现了基本的功能和图形界面 。本篇主要介绍如何实现插件与JS交互。交互功能主要是通过NPObject对象来实现的。
上面提到我们重写了NPP_GetValue函数,使NPP_GetValue去调用CPlugin中的GetScriptObject,而不是按默认流程调用GetValue(其实无所谓,改个名称而已)。所以,当插件加载完成后,浏览器会调用NPP_GetValue并将variable参数设置为NPPVpluginScriptableNPObject来查询浏览器是否支持与脚本语言的交互功能,如果插件支持交互功能,则NPP_GetValue会返回一个NPOject对象给浏览器,此时浏览器就可以通过这个NPObject对象与插件建立联系。所以在CPlugin::GetScriptObject函数中,我们要实现NPObject对象。实现如下:
NPObject *CPlugin::GetScriptableObject() { if (!m_pScriptableObject) { m_pScriptableObject = NPN_CreateObject(m_pNPInstance,GET_NPOBJECT_CLASS(CScriptObject)); } if (m_pScriptableObject) { NPN_RetainObject(m_pScriptableObject); } return m_pScriptableObject; }
当存在NPObject对象时,直接使用NPN_RetainObject获取并返回。若不存在NPObject对象时,使用NPN_CreateObject创建一个NPObject对象。GET_NPOBJECT_CLASS宏定义如下:
#define DECLARE_NPOBJECT_CLASS_WITH_BASE(_class, ctor) \ static NPClass s##_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 \ }
宏中的ScriptablePluginObjectBase定义如下:
class ScriptablePluginObjectBase : public NPObject { public: ScriptablePluginObjectBase(NPP npp) : mNpp(npp) { } virtual ~ScriptablePluginObjectBase() { } // Virtual NPObject hooks called through this base class. Override // as you see fit. //virtual NPObject *allocate(NPP npp, NPClass *aClass){} virtual void invalidate(); virtual bool hasMethod(NPIdentifier name); virtual bool invoke(NPIdentifier name, const NPVariant *args,uint32_t argCount, NPVariant *result); virtual bool invokeDefault(const NPVariant *args, uint32_t argCount,NPVariant *result); virtual bool hasProperty(NPIdentifier name); virtual bool getProperty(NPIdentifier name, NPVariant *result); virtual bool setProperty(NPIdentifier name, const NPVariant *value); virtual bool removeProperty(NPIdentifier name); virtual bool enumerate(NPIdentifier **identifier, uint32_t *count); virtual bool construct(const NPVariant *args, uint32_t argCount,NPVariant *result); public: static NPObject *_allocate(NPP npp, NPClass *aClass); static void _deallocate(NPObject *npobj); static void _invalidate(NPObject *npobj); static bool _hasMethod(NPObject *npobj, NPIdentifier name); static bool _invoke(NPObject *npobj, NPIdentifier name,const NPVariant *args, uint32_t argCount,NPVariant *result); static bool _invokeDefault(NPObject *npobj, const NPVariant *args,uint32_t argCount, NPVariant *result); static bool _hasProperty(NPObject * npobj, NPIdentifier name); static bool _getProperty(NPObject *npobj, NPIdentifier name,NPVariant *result); static bool _setProperty(NPObject *npobj, NPIdentifier name,const NPVariant *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_t argCount, NPVariant *result); static NPObject *AllocateCScriptObject(NPP npp, NPClass *aClass); protected: NPP mNpp; };
该类需要继承自NPObject,因为插件需要给浏览器返回一个NPObject对象。随后再定一个类CScriptObject。
class CScriptObject: public ScriptablePluginObjectBase { public: NPUTF8 *utf8_chars1,*utf8_chars2,*utf8_chars3,*utf8_chars4;//缓冲区 static NPObject *sWindowObj; static NPIdentifier s_idGetPasswd; static NPIdentifier s_idGetMacAddress; static NPIdentifier s_idGetHardwareInfo; static NPIdentifier s_idSetEditSize; static NPIdentifier s_idGetVersion; //int result_size; //NPString str1; //NPUTF8 utf8_chars1[INVOKE_STRING_LENGTH]; CScriptObject(NPP npp); ~CScriptObject(); virtual bool hasProperty(NPIdentifier name); virtual bool hasMethod(NPIdentifier name); virtual bool getProperty(NPIdentifier name, NPVariant *result); virtual bool setProperty(NPIdentifier name, const NPVariant *value); virtual bool invoke(NPIdentifier name, const NPVariant *args, uint32_t argCount, NPVariant *result); virtual bool invokeDefault(const NPVariant *args, uint32_t argCount, NPVariant *result); virtual bool enumerate(NPIdentifier **identifier, uint32_t *count); virtual bool construct(const NPVariant *args, uint32_t argCount,NPVariant *result); };
CScriptObject对象继承ScriptablePluginObjectBase类,随后就是实现CScriptObject中的虚函数,以下为部分实现:
CScriptObject::CScriptObject(NPP npp):ScriptablePluginObjectBase(npp) { s_idGetPasswd = NPN_GetStringIdentifier("GetPasswd"); s_idGetMacAddress = NPN_GetStringIdentifier("GetMacAddress"); s_idGetHardwareInfo = NPN_GetStringIdentifier("GetHardwareInfo"); s_idSetEditSize = NPN_GetStringIdentifier("SetEditSize"); s_idGetVersion = NPN_GetStringIdentifier("GetVersion"); utf8_chars1 = static_cast<NPUTF8 *>(NPN_MemAlloc(INVOKE_STRING_LENGTH)); utf8_chars2 = static_cast<NPUTF8 *>(NPN_MemAlloc(INVOKE_STRING_LENGTH)); utf8_chars3 = static_cast<NPUTF8 *>(NPN_MemAlloc(INVOKE_STRING_LENGTH)); utf8_chars4 = static_cast<NPUTF8 *>(NPN_MemAlloc(INVOKE_STRING_LENGTH)); } CScriptObject::~CScriptObject() { //NPN_MemAlloc分配的内存不需要手动释放,浏览器会自动释放 //NPN_MemFree(utf8_chars1); //NPN_MemFree(utf8_chars2); //NPN_MemFree(utf8_chars3); //MessageBox(NULL,"CScriptobject delete","Plugin GetMacAddress",MB_ICONERROR|MB_OK); } bool CScriptObject::hasMethod(NPIdentifier name) { //MessageBox(NULL,"HashMethod Successful","HashMethod",MB_ICONERROR|MB_OK); return name == s_idGetPasswd || name == s_idGetMacAddress || name == s_idGetHardwareInfo || name == s_idSetEditSize || name == s_idGetVersion; } bool CScriptObject::hasProperty(NPIdentifier name) { //return name == s_idSetEditSize; return false; } bool CScriptObject::getProperty(NPIdentifier name, NPVariant *result) { return false; } bool CScriptObject::setProperty(NPIdentifier name, const NPVariant *value) { return false; } bool CScriptObject::invoke(NPIdentifier name, const NPVariant *args, uint32_t argCount, NPVariant *result) { char *tmp; CPlugin* plugin = (CPlugin*) mNpp->pdata; if (name == s_idGetPasswd) { tmp = plugin->myedit->GetVal(); //*result = NPVARIANT_TO_STRING(tmp); memset(utf8_chars1,'\0',INVOKE_STRING_LENGTH); strcpy(utf8_chars1,tmp); //STRINGZ_TO_NPVARIANT(utf8_chars1,*result); //INT32_TO_NPVARIANT(10,*result); result->type = NPVariantType_String; NPString str1 = {utf8_chars1, uint32_t(strlen(utf8_chars1)) }; //str1.UTF8Characters = utf8_chars1; //str1.UTF8Length = uint32_t(strlen(utf8_chars1)); result->value.stringValue = str1; return true; } if(name == s_idGetMacAddress){ tmp = plugin->myedit->GetMacAddress(); memset(utf8_chars2,'\0',INVOKE_STRING_LENGTH); strcpy(utf8_chars2,tmp); //STRINGZ_TO_NPVARIANT(utf8_chars2,*result); result->type = NPVariantType_String; NPString str2 = { utf8_chars2, uint32_t(strlen(utf8_chars2)) }; result->value.stringValue = str2; return true; } if(name == s_idGetHardwareInfo){ tmp = plugin->myedit->GetHardwareInfo(); memset(utf8_chars3,'\0',INVOKE_STRING_LENGTH); strcpy(utf8_chars3,tmp); //STRINGZ_TO_NPVARIANT(utf8_chars3,*result); result->type = NPVariantType_String; NPString str3 = { utf8_chars3, uint32_t(strlen(utf8_chars3)) }; result->value.stringValue = str3; return true; } if(name == s_idSetEditSize){ //plugin->myedit->SetEditSize(); if(args[0].type == NPVariantType_Int32 && args[1].type == NPVariantType_Int32){ plugin->myedit->SetEditSize(NPVARIANT_TO_INT32(args[0]),NPVARIANT_TO_INT32(args[1])); }else if (args[0].type == NPVariantType_Double && args[1].type == NPVariantType_Double){ plugin->myedit->SetEditSize((int)NPVARIANT_TO_DOUBLE(args[0]),(int)NPVARIANT_TO_DOUBLE(args[1])); } //INT32_TO_NPVARIANT(result_size,*result); return true; } if(name == s_idGetVersion){ tmp = plugin->myedit->GetVersion(); memset(utf8_chars4,'\0',INVOKE_STRING_LENGTH); strcpy(utf8_chars4,tmp); result->type = NPVariantType_String; NPString str4 = { utf8_chars4, uint32_t(strlen(utf8_chars4)) }; result->value.stringValue = str4; return true; } return false; }
其中最主要的是hasMethod和invoke两个函数。当浏览器中JS调用某个插件函数A时,浏览器会通过NPObject调用hasMethod函数,以判断插件是否提供函数A的调用,若插件存在函数A(即hasMethod返回true),随后浏览器将调用invoke函数。故,在hasMethod函数中,我们将插件对外提供的函数在此处列出。
return name == s_idGetPasswd || name == s_idGetMacAddress || name == s_idGetHardwareInfo || name == s_idSetEditSize || name == s_idGetVersion;
其中s_idGetPasswd等变量在CScriptObject进行了相应的初始化,以关联具体函数名。
CScriptObject::CScriptObject(NPP npp):ScriptablePluginObjectBase(npp) { s_idGetPasswd = NPN_GetStringIdentifier("GetPasswd"); s_idGetMacAddress = NPN_GetStringIdentifier("GetMacAddress"); s_idGetHardwareInfo = NPN_GetStringIdentifier("GetHardwareInfo"); s_idSetEditSize = NPN_GetStringIdentifier("SetEditSize"); s_idGetVersion = NPN_GetStringIdentifier("GetVersion"); ........ }
当通过hasMethod返回true之后,浏览器讲会调用invoke函数,在invoke函数中,通过判断name参数,确定浏览器需要具体调用插件中的哪个函数,随后调用该函数即可,具体可参照invoke实现代码。插件提供的各种函数在CYKedit中详细定义。