上一篇文章中的JsEngine2对SpiderMonkey稍加扩展,使得JS代码里可以使用alert()函数,但是并没有仔细解释如何使用JSNative。这一篇文章我们看看如何给JS提供一个max()函数,经一步研究JSNative。
1)添加JsEngine3类
添加一个新的C++类,命名为JsEngine3,并让它继承JsEngine2:
// JsEngine3.h
#include "JsEngine2.h"
class JsEngine3 : public JsEngine2 {
public:
JsEngine3();
virtual ~JsEngine3();
};
2)添加C++版max()函数
代码如下所示,比较简单,就不多解释了:
// JsEngine3.cpp
#include "JsEngine3.h"
static double max(double a, double b) {
return a > b ? a : b;
}
3)添加JSNative版max()函数
static JSBool myjs_max(JSContext *cx, unsigned argc, jsval *vp) {
double a, b;
if (!JS_ConvertArguments(cx, argc, JS_ARGV(cx, vp), "dd", &a, &b))
return JS_FALSE;
double c = max(a, b);
JS_SET_RVAL(cx, vp, DOUBLE_TO_JSVAL(c));
return JS_TRUE;
}
让我们重点研究一下上面这个函数的实现,关于
JSNative的更多解释可以看这里。首先定义了两个double型临时变量a和b,接着调用JS_ConvertArguments()从vp中提取出a和b。JS_ConvertArguments()函数的使用方式有点像printf,它的第四个参数是一个字符串,后面是VARGs。这里我们传给它一个“dd”,代表两个double,分别赋给a和b。下面是从jsapi.h里面摘出来的JS_ConvertArguments()函数的声明和注释:
/*
* Format is a string of the following characters (spaces are insignificant),
* specifying the tabulated type conversions:
*
* b JSBool Boolean
* c uint16_t/jschar ECMA uint16_t, Unicode char
* i int32_t ECMA int32_t
* u uint32_t ECMA uint32_t
* j int32_t Rounded int32_t (coordinate)
* d double IEEE double
* I double Integral IEEE double
* S JSString * Unicode string, accessed by a JSString pointer
* W jschar * Unicode character vector, 0-terminated (W for wide)
* o JSObject * Object reference
* f JSFunction * Function private
* v jsval Argument value (no conversion)
* * N/A Skip this argument (no vararg)
* / N/A End of required arguments
*
* The variable argument list after format must consist of &b, &c, &s, e.g.,
* where those variables have the types given above. For the pointer types
* char *, JSString *, and JSObject *, the pointed-at memory returned belongs
* to the JS runtime, not to the calling native code. The runtime promises
* to keep this memory valid so long as argv refers to allocated stack space
* (so long as the native function is active).
*
* Fewer arguments than format specifies may be passed only if there is a /
* in format after the last required argument specifier and argc is at least
* the number of required arguments. More arguments than format specifies
* may be passed without error; it is up to the caller to deal with trailing
* unconverted arguments.
*/
extern JS_PUBLIC_API(JSBool)
JS_ConvertArguments(JSContext *cx, unsigned argc, jsval *argv, const char *format, ...);
拿到参数a和b以后,调用C++版max()函数求出二者中较大的那个,赋值给临时变量c。然后使用JS_SET_VAL宏设置JS函数的返回值。DOUBLE_TO_JSVAL函数可以把double值转换为jsval。jsval的详细说明可以
看这里。最后返回JS_TRUE告诉SpiderMonkey函数调用成功。
4)注册myjs_max
static JSFunctionSpec myjs_global_functions[] = {
JS_FS("max", myjs_max, 2, 0),
JS_FS_END
};
JsEngine3::JsEngine3() {
if (!JS_DefineFunctions(cx, global, myjs_global_functions)) {
return;
}
}
JS_FS宏可以产生一个
JSFunctionSpec,JS_FS_END宏产生一个类似NULL的JSFunctionSpec。最后调用
JS_DefineFunctions函数注册max函数。完整的JsEngine3.cpp代码如下:
#include "JsEngine3.h"
static double max(double a, double b) {
return a > b ? a : b;
}
static JSBool myjs_max(JSContext *cx, unsigned argc, jsval *vp) {
double a, b;
if (!JS_ConvertArguments(cx, argc, JS_ARGV(cx, vp), "dd", &a, &b))
return JS_FALSE;
double c = max(a, b);
JS_SET_RVAL(cx, vp, DOUBLE_TO_JSVAL(c));
return JS_TRUE;
}
static JSFunctionSpec myjs_global_functions[] = {
JS_FS("max", myjs_max, 2, 0),
JS_FS_END
};
JsEngine3::JsEngine3() {
if (!JS_DefineFunctions(cx, global, myjs_global_functions)) {
return;
}
}
JsEngine3::~JsEngine3() {
}
5)测试max函数
修改ViewController.mm测试一下:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
JsEngine3 engine;
engine.execJS("alert(max(3.1415, 4));");
}
运行模拟器,输出中打印出了4!