(1)c/c++类
假定c/c++定义了一个Person类,这个类如下:
class Person {
public:
Person(){
std::cout << "constructor 1." << std::endl;
}
Person(char* name, int age) {
std::cout << "constructor 2." << std::endl;
strcpy_s(_name, name);
_age = age;
}
~Person(){
std::cout << "destroy the object." << std::endl;
}
char* getName() {
std::cout << "get name." << std::endl;
return _name;
}
int getAge() {
std::cout << "get age." << std::endl;
return _age;
}
void setName(char* name) {
std::cout << "set name." << std::endl;
strcpy_s(_name, name);
}
void setAge(int age) {
std::cout << "set age." << std::endl;
_age = age;
}
void print() {
std::cout << "The person's name is " << _name << "." << std::endl;
std::cout << "The person's age is " << _age << "." << std::endl;
}
private:
char _name[20];
int _age;
};
可以看到,Person类有2个构造函数,一个析构函数,四个属性函数和一个打印输出函数。
(2)js代码
现在要用JavaScript来使用这个类。假定JavaScript代码如下:
var pers1 = new Person("Fang", 24);
var name = pers1.GetName();
var age = pers1.GetAge();
pers1.Print();
var pers2 = new Person();
pers2.SetName("Liu");
pers2.SetAge(20);
pers2.Print();
上述JavaScript代码保存在一个tc.js文件中。
(3)初步分析
要使用js调用c/c++的类,我们必须清楚spidermonkey的内部机制是层层嵌套的对象。我们要使用自己定义的类(对象),还要保证js一般属性实现。因此,我们定义的类在spidermonkey内部就不能是最顶层的对象,而是建立在顶层对象下的一个子对象。
从前面的学习我们已经知道,从js调用c/c++的函数就需要将函数按照spidermonkey的格式(具体函数写法)将函数显示给js(绑定到对象上,前面遇到的对象只有顶层的)。现在是将c/++的类嵌入到spidermonkey,那又会有什么不同呢?
实际上,我们都知道类是对函数(构造、析构、属性等各种函数和成员变量)进行了封装而已,其本质仍然是一些函数,因此,我们只要掌握对应函数的格式(具体函数写法)和显示(绑定到对象上)即可,明白了这些,剩下的就不难了。
下面,以我们上面的c/c++的Person类来看,在SpiderMonkey中嵌入这个类有什么要注意的。实际上,我们可以把Person的各种函数分为三类:构造、析构和其他函数。为什么这样分呢?因为,构造函数是首先使用的,即调用者要使用new的;析构函数是不需使用者调用,由程序自行调用的;剩下的,不论是属性函数还是普通函数,其本质没有区别。事实上spidermonkey就是这样处理的,因此,我们只要掌握了spidermonkey是怎样处理构造函数和析构函数的格式和显示的,我们就可以掌握类的实现,因为其他函数我们已经学会了。
(4)类中一般函数的实现
我们先从已经知道的开始,即把除构造和析构函数外的函数,先实现。
首先要把Person这个类找到,我们使用下面函数
static Person* getPriv(JSObject* obj) {
return static_cast<Person*>(JS_GetPrivate(obj));
}
然后,我们就可以实现那五个一般函数了。
static bool jsPrint(JSContext* cx, unsigned argc, JS::Value* vp) {
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
//下面的jsapi就是找到Person所在的对象
JS::RootedObject thisObj(cx, &args.computeThis(cx).toObject());
getPriv(thisObj)->print();
return true;
}
static bool jsGetName(JSContext* cx, unsigned argc, JS::Value* vp) {
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
JS::RootedObject thisObj(cx, &args.computeThis(cx).toObject());
char* name = getPriv(thisObj)->getName();
args.rval().setString(JS_NewStringCopyN(cx, name, strlen(name)));
return true;
}
static bool jsGetAge(JSContext* cx, unsigned argc, JS::Value* vp) {
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
JS::RootedObject thisObj(cx, &args.computeThis(cx).toObject());
int age = getPriv(thisObj)->getAge();
args.rval().setInt32(age);
return true;
}
static bool jsSetName(JSContext* cx, unsigned argc, JS::Value* vp) {
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
JS::RootedObject thisObj(cx, &args.computeThis(cx).toObject());
JSString* jstr = args[0].toString();
getPriv(thisObj)->setName(JS_EncodeString(cx, jstr));
return true;
}
static bool jsSetAge(JSContext* cx, unsigned argc, JS::Value* vp) {
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
JS::RootedObject thisObj(cx, &args.computeThis(cx).toObject());
getPriv(thisObj)->setAge(args[0].toInt32());
return true;
}
上面的五个函数和以前笔记(六)的js调用c/c++函数是一样的,只是多了一个根据函数参数寻找到对应对象的jsapi而已。
另外,对于无需函数返回值的,就不需要args.rval()其返回即可。这么多的函数显示,我们也是熟悉的,只是不用JS_DefineFunction显示,而是在类初始化中显示,但都要用到
static const JSFunctionSpec functions[] = {
JS_FN("Print", jsPrint, 0, 0),
JS_FN("SetAge", jsSetAge, 1, 0),
JS_FN("GetAge", jsGetAge, 0, 0),
JS_FN("SetName", jsSetName, 1, 0),
JS_FN("GetName", jsGetName, 0, 0),
JS_FS_END
};
(5)类中析构函数的实现
析构函数,在spidermonkey内,是在类的形式中显示出来的,即
static const JSClassOps personClassOps = {
nullptr, // addProperty
nullptr, // deleteProperty
nullptr, // enumerate
nullptr, // newEnumerate
nullptr, // resolve
nullptr, // mayResolve
finalize, // finalize析构函数(函数名可以随意)
nullptr, // call
nullptr, // hasInstance
nullptr, // construct构造函数,放在这里没有用,等会看到
JS_GlobalObjectTraceHook // trace
};
那么,析构函数的格式是怎样的呢?它是固定的,这里我们只要找到c/c++的Person,然后毁掉指针即可。
static void finalize(JSFreeOp* fop, JSObject* obj) {
//找到Person
Person* priv = getPriv(obj);
if (priv) {
delete priv;
JS_SetPrivate(obj, nullptr);
}
}
(6)类中构造函数的实现
我们jsapi.h这个头文件中查找JSClassOps就可以看到
struct JS_STATIC_CLASS JSClassOps {
/* Function pointer members (may be null). */
JSAddPropertyOp addProperty;
JSDeletePropertyOp delProperty;
JSEnumerateOp enumerate;
JSNewEnumerateOp newEnumerate;
JSResolveOp resolve;
JSMayResolveOp mayResolve;
JSFinalizeOp finalize;
JSNative call;
JSHasInstanceOp hasInstance;
JSNative construct;
JSTraceOp trace;
};
对于构造函数,实际上也是JSNative,那它的格式就和前面的一般函数是一样的,只是显示(绑定)的方式不同于一般函数而已。这样我们就先把它写出来,注意,我们这里的构造函数有重载,因此,在引擎内部也要重载。
static bool constructor(JSContext* cx, unsigned argc, JS::Value* vp) {
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
if (!args.isConstructing()) {
std::cout << "It isn't a constructor." << std::endl;
return false;
}
//不同于一般函数,一般函数是找到对象,而构造函数是要创造对象,因此它是一个新对象
JS::RootedObject newObj(cx, JS_NewObjectForConstructor(cx, &klass, args));
if (!newObj) {
return false;
}
//定义Person指针
Person* priv;
//根据参数不同实现重载
if (args.length() == 2) {
priv = new Person(JS_EncodeString(cx, args[0].toString()), args[1].toInt32());
}
else {
priv = new Person();
}
//根据Person在引擎内部创建对象
JS_SetPrivate(newObj, priv);
//最后将创建的结果返回
args.rval().setObject(*newObj);
return true;
}
从上面构造函数的实现JS_NewObjectForConstructor(cx, &klass, args),在写构造函数之前,还要把引擎内的类定义出来。
static JSClass klass = {
"Person",
JSCLASS_HAS_PRIVATE | JSCLASS_FOREGROUND_FINALIZE,
&personClassOps
};
这个类我们用klass命名,它是global object下的一个私有对象,大家再回头看笔记(二)中那个模板,其中
JSObject* CreateGlobal(JSContext* cx) {
JS::CompartmentOptions ops;
static JSClass GlobalClass = { "global", JSCLASS_GLOBAL_FLAGS, &DefaultGlobalClassOps };
return JS_NewGlobalObject(cx, &GlobalClass, nullptr, JS::FireOnNewGlobalHook, ops);
}
就是顶层的object,我们现在的Person就是在其之下的对象。
写出构造函数后,再看怎么显示出来呢?,我们也看出一点端倪了,构造函数的显示一定与引擎内的类有关。就是要初始化这个对象。在笔记(二)中初始化global object的是RunExample方法中这句:JS::InitSelfHostedCode(cx)。现在的Person是在global object之下的对象,因此,这个对象的初始化要在global object内初始化。其JSAPI为
extern JS_PUBLIC_API JSObject* JS_InitClass(
JSContext* cx, JS::HandleObject obj, JS::HandleObject parent_proto,
const JSClass* clasp, JSNative constructor, unsigned nargs,
const JSPropertySpec* ps, const JSFunctionSpec* fs,
const JSPropertySpec* static_ps, const JSFunctionSpec* static_fs);
因此,我们先建一个初始化它的函数
static bool InitPerson(JSContext* cx, JS::HandleObject globs) {
if(JS_InitClass(
cx,
globs, // the object in which to define the class
nullptr, // the prototype of the parent class
// (in our case, no parent class)
&klass, // the JSClass defined above
constructor, //这个是构造函数显示的地方。下面的应该是参数个数,但选几是无所谓的。
0, // constructor and num. args //Person有两种参数,0或者2,但这里设0或者2都没有问题
// The four nullptrs below are for arrays where you
// would list predefined (not lazy) methods and
// properties, static and non-static
nullptr,
functions, //一般函数的显示,因此,类函数显示与非类函数的显示不同
nullptr,
nullptr) == nullptr){
abort();
}
return true;
}
这个函数,我们只要在笔记(二)那个模板中的HelloExample方法添加即可(我把方法名称也改了)
static bool ClassExample(JSContext* cx) {
JSAutoRequest ar(cx);
JS::RootedObject global(cx, CreateGlobal(cx));
if (!global) {
return false;
}
JSAutoCompartment ac(cx, global);
InitPerson(cx, global);
return ExecuteCodePrintResult(cx, "tc.js");
}
int main()
{
if (!RunExample(ClassExample)) {
return 1;
}
return 0;
}
(7)运行结果