以前的文章可以与本文互为参考:脚本化接口、插件接口脚本化。
本文的例子代码可以下载:无boost需安装CMake、原来的scriptable demo。
function newNPObject(mimetype)
{
var obj = document.createElement("object");
obj.type = mimetype;
document.body.appendChild(obj);
return obj;
}
if (variable==NPPVpluginScriptableNPObject)
{
*(NPObject **)value = plugin->GetScriptableObject();
}
NPObject* CPlugin::GetScriptableObject()
{
if (!m_pScriptableObject) {
m_pScriptableObject = NPN_CreateObject(m_pNPInstance, &CScriptObject::nsScriptObjectClass);
}
if (m_pScriptableObject) {
NPN_RetainObject(m_pScriptableObject);
}
return m_pScriptableObject;
}
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实际上就是与浏览器获取到的这个对象来交互的。
static NPIdentifier foo_id;
NPIdentifier CScriptObject::foo_id;
int m_vfoo;
CScriptObject::foo_id = NPN_GetStringIdentifier("foo");
bool CScriptObject::HasProperty(NPIdentifier name)
{
return name == foo_id;
}
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;
}
}
bool CScriptObject::GetProperty(NPIdentifier name, NPVariant *result)
{
if (name == foo_id)
{
INT32_TO_NPVARIANT(m_vfoo,*result);
m_vfoo++;
return true;
}
}
property test:
int property
function btnsetfoo()
{
var val = document.getElementById("fooinput");
obj.foo = parseInt(val.value) ;
}
function btngetfoo()
{
alert(obj.foo);
}
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中相应代码为:
JS代码(片段)如下:
function btnclick()
{
obj.func();
}
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函数就可以了。
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中有乱码的问题,请及时反馈给我,需要的话以后再来补充。
class JsArrayObject : public NPObject
{
private:
vector array_;
static NPIdentifier method_At;
static NPIdentifier method_Size;
public:
static NPClass nsJsObject;
JsArrayObject();
~JsArrayObject(){}
public:
std::wstring At(size_t idx);
size_t Size();
void Clear();
bool Empty();
void PushBack(std::wstring val);
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 **value, uint32_t *count);
static bool _Construct(NPObject *npobj, const NPVariant *args, uint32_t argCount, NPVariant *result);
};
NPIdentifier JsArrayObject::method_At;
NPIdentifier JsArrayObject::method_Size;
JsArrayObject::JsArrayObject()
{
method_At = NPN_GetStringIdentifier("At");
method_Size = NPN_GetStringIdentifier("Size");
}
NPObject* JsArrayObject::_Allocate(NPP npp, NPClass *aClass)
{
return (NPObject*)new JsArrayObject();
}
void JsArrayObject::_Deallocate(NPObject *npobj)
{
delete (JsArrayObject*)npobj;
}
void JsArrayObject::_Invalidate(NPObject *npobj)
{
}
bool JsArrayObject::_HasMethod(NPObject *npobj, NPIdentifier name)
{
return name == method_At || name ==method_Size ;
}
bool JsArrayObject::_Invoke(NPObject *npobj, NPIdentifier name, const NPVariant *args, uint32_t argCount, NPVariant *result)
{
if (name == method_At)
{
if(argCount < 1) return false;
int val = args[0].value.intValue;
std::wstring str = ((JsArrayObject*)npobj)->At(val);
std::string pstr = ult::UnicodeToUtf8(str);
char* npOutString = (char*) NPN_MemAlloc(pstr.length() + 1);
if (!npOutString)
return false;
strcpy(npOutString, pstr.c_str());
STRINGZ_TO_NPVARIANT(npOutString,*result);
return true;
}
if (name == method_Size)
{
int val = ((JsArrayObject*)npobj)->Size();
INT32_TO_NPVARIANT(val, *result);
return true;
}
return false;
}
bool JsArrayObject::_InvokeDefault(NPObject *npobj, const NPVariant *args, uint32_t argCount, NPVariant *result)
{
return false;
}
bool JsArrayObject::_HasProperty(NPObject *npobj, NPIdentifier name)
{
return false;
}
bool JsArrayObject::_GetProperty(NPObject *npobj, NPIdentifier name, NPVariant *result)
{
return false;
}
bool JsArrayObject::_SetProperty(NPObject *npobj, NPIdentifier name, const NPVariant *value)
{
return false;
}
bool JsArrayObject::_RemoveProperty(NPObject *npobj, NPIdentifier name)
{
return false;
}
bool JsArrayObject::_Enumerate(NPObject *npobj, NPIdentifier **value, uint32_t *count)
{
return false;
}
bool JsArrayObject::_Construct(NPObject *npobj, const NPVariant *args, uint32_t argCount, NPVariant *result)
{
return false;
}
NPClass JsArrayObject::nsJsObject = {
NP_CLASS_STRUCT_VERSION_CTOR,
JsArrayObject::_Allocate,
JsArrayObject::_Deallocate,
JsArrayObject::_Invalidate,
JsArrayObject::_HasMethod,
JsArrayObject::_Invoke,
JsArrayObject::_InvokeDefault,
JsArrayObject::_HasProperty,
JsArrayObject::_GetProperty,
JsArrayObject::_SetProperty,
JsArrayObject::_RemoveProperty,
JsArrayObject::_Enumerate,
JsArrayObject::_Construct
};
std::wstring JsArrayObject::At(size_t idx)
{
return array_.at(idx);
}