打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
学习SpiderMonkey60的心得笔记(九)JavaScript调用C/C++类
(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)运行结果

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
脚本引擎小PK: SpiderMonkey vs V8(二)
SpiderMonkey-让你的C++程序支持JavaScript脚本
58JavaScript
第124讲 DirectX11Frame(4)
std::unique_ptr使用incomplete type的报错分析和解决
设计模式之单例模式(C++)
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服