相信初学javascript的工作于嵌入式系统的人,当然包括我自己,都有一个疑问,如果应用是html/css/javascrip写,而中间件是c/c++写,那么javascript与c/c++中间件API是如何相互调用的呢?通过一段时间的学习,在此做个总结
#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
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 { ... }
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
一个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; }
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); }
var servers=$BaseTv.GetDevices()
[{ id: "…", name: "…", desc: "…" }, …];
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) { … }; }
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); }
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调用的次数必需相等。