相信初学javascript的工作于嵌入式系统的人,当然包括我自己,都有一个疑问,如果应用是html/css/javascrip写,而中间件是c/c++写,那么javascript与c/c++中间件API是如何相互调用的呢?通过一段时间的学习,在此做个总结
1.JavaScriptCore与头文件
#include <WebKit.h>
#include <JavaScriptCore/JavaScript.h>
JavaScriptCore API: http://developer.apple.com/library/mac/#documentation/Carbon/Reference/WebKit_JavaScriptCore_Ref/JSObjectRef_h/index.html
2.在c++中创建javascript类
2.1定义一个javascript类
static JSClassDefinition jsBaseTvDefinition =
{
0, //version
kJSClassAttributeNone, //attributes
"__BaseTvClass", //className
0, //parentClass
0, //staticValues
jsBaseTvFunctions, //staticFunctions
0, //Initialize
finalize, //Finalize
0, //has Property
0, //get Property
0, //set Property
0, //delete Property
0, //getPropertyNames
0, //callAsFunction
0, //hasInstance
0, //callAsConstructor
0 //convertToType
}
这里我定义了一个叫"__BaseTvClass"的类和一个叫finalize的方法(前缀"__"不是必需的,只是为了防止与javascript中定义的类冲突)。在javascript中,它相当于下面这个样子
function __BaseTvClass
{
...
}
2.2定义成员函数
static JSStaticFunction jsBaseTvFunctions[]
{
{"setSource",JSBaseTv::setSource,kJSPropertyAttributeNone},
{"getCurrentSource",JSBaseTv::getCurrentSource,kJSPropertyAttributeNone},
{"startChannelScan",JSBaseTv::startChannelScan,kJSPropertyAttributeNone},
{"abortChannelScan",JSBaseTv::abortChannelScan,kJSPropertyAttributeNone},
{0,0,0}
}
这是个函数名的数组和对应的c/c++函数,它在javascript中相当于:
function __BaseTvClass()
{
this.setSource=function()
{
}
this.getCurrentSource=function()
{
}
this.startChannelScan=function()
{
}
this.abortChannelScan=function()
{
}
}
typedef struct {
const char *const name;
JSObjectCallAsFunctionCallback callAsFunction;
JSPropertyAttributes attributes;
} JSStaticFunction
name:一个字符串,表示属性的名字;
一个callback函数,即当name作为一个函数调用时,callAsFunction会被调用。可以理解为name与callAsFunction的绑定,name对javascript可见,当javascript中调用name时,即相当于callAsFunction被调用。
attributes:name的一个属性集
下面介绍下函数的实现,在c++中每个函数都有一个的声明,参数都是一致的:
JSValueRef JSBaseTv::startChannelScan(JSContextRef ctx,JSObjectRef function,JSObjectRef thisObject,size_t argumentCount,
const JSValueRef arguments[],JSValueRef *exception)
{
if(argumentCount < 1)
return JSValueMakeNull(ctx);
JSStringRef jsStr = JSValueToStringCopy(ctx,arguments[0],NULL);
char scanType[32];
JSStringGetUTF8CString(jsStr,scanType,sizeof(scanType)-1);
if(!strcmp(scanType,"atv"))
{
/* */
}
else if(!strcmp(scanType,"dtv"))
{
/* */
}
else
{
/* */
}
return JSValueMakeNull(ctx);
}
JSValueRefJSBaseTv::abortChannelScan(JSContextRef ctx,JSObjectRef function,JSObjectRefthisObject,size_t argumentCount,const JSValueRef arguments[],JSValueRef*exception){/*argumentCount代表了传入参数的个数,arguments[]存储了具体的参数,arguments[0]是第一个参数,arguments[1]是第二个参数,以此类推*/}
你可以看到每个函数参数的样式一样的,都是5个参数。
void JSBaseTv::finalize(JSObjectRef thisObject) {
// delete me;
}
3.创建javascript类的对象实例
void JSBaseTv::Setup()
{
globalContext = JSGlobalContextCreate(0);
JSObjectRef global = JSContextGetGlobalOjbect(globalContext);
JSClassRef baseTvClass = JSClassCreate(&jsBaseTvDefinition);
JSOjbectRef baseTv = JSObjectMake(globalContext,baseTvClass,NULL);
JSStringRef name = JSStringCreateWithUTF8CString("$BaseTv");
JSObjectSetProperty(globalContext,global,name,baseTv,kJSPropertyAttributeNone,0);
JSStringRelease(name);
JSClassRelease(baseTvClass);
}
这段代码创建了一个jsBaseTvDefinition的类,并且创建了一个全局变量$BaseTv,"$"不是必需的。$BaseTv实际上就是jsBaseTvDefinition的实例化,放在javascript中,它相当的作用:
$BaseTv = new jsBaseTvDefinition();
现在全局变量$BaseTv对HTML里的javascript是可见的,你可以直接以名字$BaseTv或者window[“$BaseTv”]来使用,例如下面这段javascript代码调用函数startChannelScan
startChannelScan:function()
{
$BaseTv.startChannelScan("atv");
}
再看下面这个函数:
JSValueRef JSBaseTv::getDevices(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) {
DLNA* me = (DLNA*) JSObjectGetPrivate(thisObject);
if (me->devices.size() == 0)
return JSValueMakeNull(ctx);
Lock lock(&me->mutex);
JSObjectRef* js = new JSObjectRef[me->devices.size()];
size_t i = 0;
for (map<string, DeviceHandle>::iterator it = me->devices.begin(); it != me->devices.end(); i++, it++) {
JSObjectRef o = JSObjectMake(ctx, NULL, NULL);
DeviceInfo info;
GetDeviceInfo(me->dmp, it->second, &info);
setProperty(ctx, o, "id", info.udn);
setProperty(ctx, o, "name", info.friendlyName);
setProperty(ctx, o, "desc", info.modelDescription);
js[i] = o;
}
JSObjectRef r = JSObjectMakeArray(ctx, i, js, exception);
delete[] js;
return r;
}
void JSBaseTv::setProperty(JSContextRef ctx, JSObjectRef o, const char* name, const char* value) {
JSStringRef n = JSStringCreateWithUTF8CString(name);
JSStringRef v = JSStringCreateWithUTF8CString(value);
JSObjectSetProperty(ctx, o, n, JSValueMakeString(ctx, v), kJSPropertyAttributeNone, 0);
JSStringRelease(n);
// JSStringRelease(v); // Do not release v as it's still needed by JSValue. It'll be released by GC.
}
void JSBaseTv::setProperty(JSContextRef ctx, JSObjectRef o, const char* name, double value) {
JSStringRef n = JSStringCreateWithUTF8CString(name);
JSObjectSetProperty(ctx, o, n, JSValueMakeNumber(ctx, value), kJSPropertyAttributeNone, 0);
JSStringRelease(n);
}
然后你可以在javascript中调用
var servers=$BaseTv.GetDevices()
现在servers就是一个对象数组,包含了属性id,name,desc
[{ id: "…", name: "…", desc: "…" }, …];
4.从c/c++调用javascript函数
JSValueRef JSBaseTv::info(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) {
if (argumentCount < 1) {
*exception = JSValueMakeString(ctx, JSStringCreateWithUTF8CString("Info(callback) argument invalid."));
return JSValueMakeNull(ctx);
}
DLNA* me = (DLNA*) JSObjectGetPrivate(thisObject);
JSObjectRef cb = const_cast<JSObjectRef>(arguments[0]);
if (!JSObjectIsFunction(ctx, cb)) {
me->infoCallback = NULL;
return JSValueMakeNull(ctx);
}
me->infoCallback = cb;
return JSValueMakeNull(ctx);
}
在javascript中,你可以调用
$BaseTv.info(msgHandler.handle);
handle这个函数就注册下去了。
var msgHandler = function() {
this.handle = function(msg, err) {
…
};
}
需要的时候,在c/c++中你可以用下面的方法调用msgHanlder.handle
void JSBaseTv::msg(const char* msg, int id) {
if (!infoCallback) return;
JSValueRef arguments[2];
JSStringRef n = JSStringCreateWithUTF8CString(msg);
arguments[0] = JSValueMakeString(globalContext, n);
arguments[1] = JSValueMakeNumber(globalContext, id);
JSObjectCallAsFunction(globalContext, infoCallback, NULL, 2, arguments, NULL);
JSStringRelease(n);
}
5.javascript对象的生命周期
JavascriptCore的GC(Garbage Collection)会负责回收你创建的对象,GC会监视任何由JSValueMakeNumber,JSValueMakeString, JSObjectMake返回的结果,如果不用的时候GC会回收它,有几种情况例外:
JSStringCreateWithUTF8CString, JSStringCreateWithCFString需要用JSStringRelease释放;
JSObjectCopyPropertyNames(一个对象属性数组)需要用JSPropertyNameArrayRelease释放;
JSCreateClass需要JSClassRelease释放;
由JSGlobalContextCreate创建的Javascript context本身需要用JSGlobalContextRelease释放.
对于什么时候该主动调用release函数,什么时候GC会自动release,可能会觉得有点混乱。可以简单地这样理解,由javascriptcontext设置或创建的对象,GC会自动回收。例如:
JSStringRef n = JSStringCreateWithUTF8CString(name);
JSValueRef ret =JSObjectGetProperty(ctx, o, n, NULL);
JSStringRelease(n);
n不是由context创建,所以需要主动回收,ret由ctx创建,GC会负责回收,在这里不需要主动调用release函数释放。
如果不想GC回收一个对象,可以使用函数JSValueProtect(JSContextRefctx, JSValueRef value)保护value不被GC回收,函授JSValueUnProtect释放保护,GC就可以回收value了,JSValueProtect与JSValueUnProtect调用的次数必需相等。