(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)运行结果 |
|
来自: Dark_f > 《JavaScript》