VC驿站

 找回密码
 加入驿站

QQ登录

只需一步,快速开始

搜索
查看: 1790|回复: 1

[分享] SpiderMonkey_31.2.0版编程笔记 七__类操作

[复制链接]
04_avatar_middle
online_vip 发表于 2015-9-17 07:07:01 | 显示全部楼层 |阅读模式
本帖最后由 liehuo 于 2015-9-17 08:37 编辑

讲述完了对象,我觉得应该说说类了,在c++里我们知道对象其实是类的实例产物。那么如何把一个c++的类与js挂钩呢,让对象可以在js里面new出来?本帖将以我的方式来说明其注入方法。先来看看我们将要注入到js里的类,我把它放在了一个头文件里,文件名叫inc.h:
  1. #include <string>
  2. class student
  3. {
  4. public:
  5.         student(char* name, int age){
  6.                 printf("构造函数被调用\n");
  7.                 m_age = age;
  8.                 strcpy(m_name, name);
  9.         }
  10.         ~student(){ printf("析构函数被调用\n\n"); };
  11.         char* GetName()
  12.         {
  13.                 return m_name;
  14.         }
  15.         int GetAge()
  16.         {
  17.                 return m_age;
  18.         }
  19.         void SetName(char *name)
  20.         {
  21.                 strcpy(m_name, name);
  22.         }
  23.         void SetAge(int age)
  24.         {
  25.                 m_age = age;
  26.         }
  27.         void SyaHello()
  28.         {
  29.                 printf("hello %s\n", m_name);
  30.         }
  31. private:
  32.         char m_name[10];
  33.         int m_age;
  34. };
复制代码

而在spidermonkey引擎这里也要有一个与之对应的JSClass类,所以在引擎这边定义一个类Student_class与它对应:
  1. static JSClass Student_class = {
  2.         "Student",
  3.         JSCLASS_HAS_PRIVATE,
  4.         JS_PropertyStub,
  5.         JS_DeletePropertyStub,
  6.         nullptr,//getter,//(JSPropertyOp)GetPro,这样也行的
  7.         nullptr,//(JSStrictPropertyOp)SetPro,
  8.         JS_EnumerateStub,
  9.         JS_ResolveStub,
  10.         JS_ConvertStub,
  11.         Destructor,//类的析构回调函数,用于销毁资源的回调函数,这里用来析构类的指针
  12.         nullptr, /* call */
  13.         nullptr, /* hasInstance */
  14.         nullptr, /* construct类的构造回调函数 ,可以填空,因为在JS_InitClass的时候会再次填补*/
  15.         nullptr,
  16. };
复制代码

在c++类的操作中无非就是定义类,提供构造函数,析构函数,成员变量操作,成员函数操作,我们要在引擎这边也是要做对应的事情。不过就是包装成引擎认识的样子而已。
第一步,包装c++类的构造函数成为引擎里的构造函数:
  1. /*
  2. c的构造函数是student(char* name, int age)里面的第一个参数是字符指针name,第二个是int类型的age,这些参数都可以在vp里得到,args[0]就是name,args[1]就是age。
  3. 然后new一个student类型指针,跟引擎里new出的JSObject对象捆绑。
  4. */
  5. bool Constructor(JSContext *cx, unsigned argc, JS::Value *vp)
  6. {
  7.         printf("constructor\n");
  8.         JS::CallArgs args = JS::CallArgsFromVp(argc, vp); //获取传进来的参数
  9.         JSString *jsName = args[0].toString();                 //获取第一参数name
  10.         int age = args[1].toInt32();                                   //获取第二参数age
  11.         char *name = JS_EncodeString(cx, jsName);
  12.         student *p = new student(name, age);                //new一个student类型指针,用做私有数据,方便下面的JS_SetPrivate捆绑私有数据操作
  13.         printf("p的地址:%X\n", p);
  14.         JSObject *obj = JS_NewObjectForConstructor(cx, &Student_class, args);//这步跟student *p = new student(name, age)差不多,都是返回一个new出的对象指针,不过是JSObject*类型的
  15.         JS_SetPrivate(obj, p);                                            //进行私有数据捆绑
  16.         //*vp = OBJECT_TO_JSVAL(obj);                          //这步跟下面的一句是一个意思,不过是做法不一样,但目的是同样的,都是把new出的对象返回出去
  17.         args.rval().setObject(*obj);    //这步非常重要,绝对不能省略,就像student *p = new student(name, age)一样,会返回一个对象实例,就是把new出的对象返回出去
  18.         return true;
  19. }
复制代码

第二步,包装c++类的析构函数成为引擎里的析构函数:
  1. /*   这步很简单,就是通过JS_GetPrivate把我们在第一步里捆绑的私有数据给取出来,因为我们捆绑的就是student类型的指针,要销毁它就要得到它,然后删除该指针置为空就行了
  2.     不过这个函数有它固有的形式,必须是void(* JSFinalizeOp)(JSFreeOp *fop, JSObject *obj);)类型,所以下面我们就是照着它来实现的函数   */
  3. void Destructor(JSFreeOp *fop, JSObject *obj)
  4. {
  5.         printf("destructor\n");
  6.         student *p = (student*)JS_GetPrivate(obj);//获取指定对象的私有数据,其实是个void类型指针,转为对应的类型指针就可以了
  7.         printf("p的地址:%X\n", p);
  8.         delete p;               //删除指针
  9.         p = nullptr;            //置为空
  10. }
复制代码
第三步,包装c++成员变量的操作,在student类里name和age字段都是私有成员变量,在类外边是不能操作的,所以类的本省提供了对其变量的get和set操作,GetName,GetAge,SetName,SetAge这四个操作,我们只需要对这四个函数操作就可以了,那我们就来包装这四个函数吧:
  1. bool GetNameProp(JSContext *cx, unsigned argc, JS::Value *vp)
  2. {
  3.         printf("getName\n");
  4.         CallArgs args = CallArgsFromVp(argc, vp);//获取调用该回调函数时传进的参数
  5.         HandleValue hv = args.thisv();          //这个thisv其实就是一个指针地址
  6.         JSObject *oj2 = &hv.toObject();         //获取this对象,哪个对象调用的,这个值就是哪个对象,跟c++里的this指针一个意思
  7.         student *p = (student*)JS_GetPrivate(oj2);//获取该this对象上捆绑的私有数据,其实是个void类型指针,转为对应的student类型指针
  8.         char* name = p->GetName();             //通过这个转换得来的指针你就可以对其自身的各项操作,为所欲为了
  9.         args.rval().setString(JS_NewStringCopyN(cx, name, strlen(name)));    //把得到的数据返回出去
  10.         return true;
  11. }
  12. bool SetNameProp(JSContext *cx, unsigned argc, JS::Value *vp)
  13. {
  14.         printf("setName\n");
  15.         CallArgs args = CallArgsFromVp(argc, vp);//获取调用该回调函数时传进的参数
  16.         JSString *str = args[0].toString();        //获取第一个参数,也就是name
  17.         char* name = JS_EncodeString(cx, str);
  18.         HandleValue hv = args.thisv();              //这个thisv其实就是一个指针地址
  19.         JSObject *oj2 = &hv.toObject();          //获取this对象,哪个对象调用的,这个值就是哪个对象,跟c++里的this指针一个意思
  20.         student *p = (student*)JS_GetPrivate(oj2);//获取该this对象上捆绑的私有数据,其实是个void类型指针,转为对应的student类型指针
  21.         p->SetName(name);                         //调用里面的方法设置student里的name
  22.         return true;
  23. }
复制代码
上面是对student里的name操作的包装函数,对age操作的都是同样的步骤,不再做注释了:
  1. bool GetAgeProp(JSContext *cx, unsigned argc, JS::Value *vp)
  2. {
  3.         printf("getAge\n");
  4.         CallArgs args = CallArgsFromVp(argc, vp);
  5.         HandleValue hv = args.thisv();
  6.         JSObject *oj2 = &hv.toObject();
  7.         student *p = (student*)JS_GetPrivate(oj2);
  8.         int n = p->GetAge();
  9.         args.rval().setInt32(n);
  10.         return true;
  11. }
  12. bool SetAgeProp(JSContext *cx, unsigned argc, JS::Value *vp)
  13. {
  14.         printf("setAge\n");
  15.         CallArgs args = CallArgsFromVp(argc, vp);
  16.         int n = args[0].toInt32();
  17.         HandleValue hv = args.thisv();
  18.         JSObject *oj2 = &hv.toObject();
  19.         student *p = (student*)JS_GetPrivate(oj2);
  20.         p->SetAge(n);
  21.         return true;
  22. }
复制代码
第四步,包装c++成员函数为引擎可以接受的函数,其实上面不是已经包装了吗:
  1. bool SayHelloFun(JSContext *cx, unsigned argc, JS::Value  *vp)
  2. {
  3.         CallArgs args = CallArgsFromVp(argc, vp);
  4.         HandleValue hv = args.thisv();
  5.         JSObject *oj2 = &hv.toObject();
  6.         student *p = (student*)JS_GetPrivate(oj2);
  7.         p->SyaHello();            //调用成员函数
  8.         return true;
  9. }
复制代码
好了通过这四步你就可以完整的把c++类包装成spidermonkey引擎能接受的类了。不过只是能接受,还没有真正注入到引擎里面去,怎么注入?很简单一个函数JS_InitClass,原型如下:
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);

其中clasp就是你定义的JSClass类Student_class,参数constructor就是构造函数Constructor,参数ps和fs就是对属性和方法的操作,后面的都不用填,对了说到ps和fs差点忘记了我们还没有属性ps和函数方法fs,赶紧定义吧,我想怎么定义不用说了吧,在前面两个关于对象操作的贴子里都已经提到了:
  1. static const JSPropertySpec my_props[] = {
  2.         JS_PSGS("age", GetAgeProp, SetAgeProp, JSPROP_ENUMERATE),
  3.         JS_PSGS("name", GetNameProp, SetNameProp, JSPROP_ENUMERATE),
  4.         JS_PS_END
  5. };
  6. static const JSFunctionSpec my_methods[] = {
  7.         JS_FS("SayHello", SayHelloFun, 0, 0),
  8.         JS_FS("SetAge", SetAgeProp, 0, 0),
  9.         JS_FS("GetAge", GetAgeProp, 0, 0),
  10.         JS_FS("SetName", SetNameProp, 0, 0),
  11.         JS_FS("GetName", GetNameProp, 0, 0),
  12.         JS_FS_END
  13. };
复制代码
通过上面的操作,已经把其属性操作和方法操作整合好了。那现在万事俱备,只欠东风了:
JSObject* protoObj = JS_InitClass(cx, global, JS::NullPtr(), &Student_class, Constructor, 0, my_props, my_methods, nullptr, nullptr);
这一下子把所有的操作都完成了,类的构造函数,属性,方法都关联起来了,你现在就可以在js脚本里试着调用了,像这样调用:
var stu=new Student('libai',888);var age=stu.GetAge();var name=stu.GetName();stu.SayHello();
还是看看完整的代码吧:
  1. #include "stdafx.h"
  2. #include "include/jsapi.h"
  3. #include "inc.h"
  4. #pragma comment(lib,"lib/mozjs-31.lib")//这里直接用这种方式,是为了观看方便
  5. using namespace JS;
  6. //int age = 0;
  7. //char *name = nullptr;
  8. static JSClass global_class = {
  9.         "global",
  10.         JSCLASS_GLOBAL_FLAGS,
  11.         JS_PropertyStub,          //增加属性
  12.         JS_DeletePropertyStub,    //删除属性
  13.         JS_PropertyStub,          //获取属性
  14.         JS_StrictPropertyStub,    //设置属性
  15.         JS_EnumerateStub,
  16.         JS_ResolveStub,
  17.         JS_ConvertStub
  18. };
  19. bool SayHelloFun(JSContext *cx, unsigned argc, JS::Value  *vp)
  20. {
  21.         CallArgs args = CallArgsFromVp(argc, vp);
  22.         HandleValue hv = args.thisv();
  23.         JSObject *oj2 = &hv.toObject();
  24.         student *p = (student*)JS_GetPrivate(oj2);
  25.         p->SyaHello();
  26.         return true;
  27. }
  28. /*这步很简单,就是通过JS_GetPrivate把我们在第一步里捆绑的私有数据给取出来,因为我们捆绑的就是student类型的指针,要销毁它就要得到它,然后删除该指针置为空就行了
  29. 不过这个函数有它固有的形式,必须是void(* JSFinalizeOp)(JSFreeOp *fop, JSObject *obj);)类型,所以下面我们就是照着它来实现的函数
  30. */
  31. void Destructor(JSFreeOp *fop, JSObject *obj)
  32. {
  33.         printf("destructor\n");
  34.         student *p = (student*)JS_GetPrivate(obj);//获取指定对象的私有数据,其实是个void类型指针,转为对应的类型指针就可以了
  35.         printf("p的地址:%X\n", p);
  36.         delete p;               //删除指针
  37.         p = nullptr;            //置为空
  38. }
  39. static JSClass Student_class = {
  40.         "Student",
  41.         JSCLASS_HAS_PRIVATE,
  42.         JS_PropertyStub,
  43.         JS_DeletePropertyStub,
  44.         nullptr,//getter,//(JSPropertyOp)GetPro,这样也行的
  45.         nullptr,//(JSStrictPropertyOp)SetPro,
  46.         JS_EnumerateStub,
  47.         JS_ResolveStub,
  48.         JS_ConvertStub,
  49.         Destructor,//用于销毁资源的回调函数,这里用来析构类的指针
  50.         nullptr, /* call */
  51.         nullptr, /* hasInstance */
  52.         nullptr, /* construct */
  53.         nullptr,
  54. };
  55. /*
  56. c的构造函数是student(char* name, int age)里面的第一个参数是字符指针name,第二个是int类型的age,这些参数都可以在vp里得到,args[0]就是name,args[1]就是age。
  57. 然后new一个student类型指针,跟引擎里new出的JSObject对象捆绑。
  58. */
  59. bool Constructor(JSContext *cx, unsigned argc, JS::Value *vp)
  60. {
  61.         printf("constructor\n");
  62.         JS::CallArgs args = JS::CallArgsFromVp(argc, vp); //获取传进来的参数
  63.         JSString *jsName = args[0].toString();            //获取第一参数name
  64.         int age = args[1].toInt32();                       //获取第二参数age
  65.         char *name = JS_EncodeString(cx, jsName);
  66.         student *p = new student(name, age);                //new一个student类型指针,用做私有数据,方便下面的JS_SetPrivate捆绑私有数据操作
  67.         printf("p的地址:%X\n", p);
  68.         JSObject *obj = JS_NewObjectForConstructor(cx, &Student_class, args);//这步跟student *p = new student(name, age)差不多,都是返回一个new出的对象指针,不过是JSObject*类型的
  69.         JS_SetPrivate(obj, p);                               //进行私有数据捆绑
  70.         //*vp = OBJECT_TO_JSVAL(obj);//这步跟下面的一句是一个意思,不过是做法不一样,但目的是同样的,都是把new出的对象返回出去
  71.         args.rval().setObject(*obj);//这步非常重要,绝对不能省略,就像student *p = new student(name, age)一样,会返回一个对象实例,就是把new出的对象返回出去
  72.         return true;
  73. }

  74. bool GetNameProp(JSContext *cx, unsigned argc, JS::Value *vp)
  75. {
  76.         printf("getName\n");
  77.         CallArgs args = CallArgsFromVp(argc, vp);//获取调用该回调函数时传进的参数
  78.         HandleValue hv = args.thisv();          //这个thisv其实就是一个指针地址
  79.         JSObject *oj2 = &hv.toObject();         //获取this对象,哪个对象调用的,这个值就是哪个对象,跟c++里的this指针一个意思
  80.         student *p = (student*)JS_GetPrivate(oj2);//获取该this对象上捆绑的私有数据,其实是个void类型指针,转为对应的student类型指针
  81.         char* name = p->GetName();             //通过这个转换得来的指针你就可以对其自身的各项操作,为所欲为了
  82.         args.rval().setString(JS_NewStringCopyN(cx, name, strlen(name)));    //把得到的数据返回出去
  83.         return true;
  84. }
  85. bool SetNameProp(JSContext *cx, unsigned argc, JS::Value *vp)
  86. {
  87.         printf("setName\n");
  88.         CallArgs args = CallArgsFromVp(argc, vp);//获取调用该回调函数时传进的参数
  89.         JSString *str = args[0].toString();        //获取第一个参数,也就是name
  90.         char* name = JS_EncodeString(cx, str);
  91.         HandleValue hv = args.thisv();              //这个thisv其实就是一个指针地址
  92.         JSObject *oj2 = &hv.toObject();          //获取this对象,哪个对象调用的,这个值就是哪个对象,跟c++里的this指针一个意思
  93.         student *p = (student*)JS_GetPrivate(oj2);//获取该this对象上捆绑的私有数据,其实是个void类型指针,转为对应的student类型指针
  94.         p->SetName(name);                         //调用里面的方法设置student里的name
  95.         return true;
  96. }
  97. bool GetAgeProp(JSContext *cx, unsigned argc, JS::Value *vp)
  98. {
  99.         printf("getAge\n");
  100.         CallArgs args = CallArgsFromVp(argc, vp);
  101.         HandleValue hv = args.thisv();
  102.         JSObject *oj2 = &hv.toObject();
  103.         student *p = (student*)JS_GetPrivate(oj2);
  104.         int n = p->GetAge();
  105.         args.rval().setInt32(n);
  106.         return true;
  107. }
  108. bool SetAgeProp(JSContext *cx, unsigned argc, JS::Value *vp)
  109. {
  110.         printf("setAge\n");
  111.         CallArgs args = CallArgsFromVp(argc, vp);
  112.         int n = args[0].toInt32();
  113.         HandleValue hv = args.thisv();
  114.         JSObject *oj2 = &hv.toObject();
  115.         student *p = (student*)JS_GetPrivate(oj2);
  116.         p->SetAge(n);
  117.         return true;
  118. }
  119. static const JSPropertySpec my_props[] = {
  120.         JS_PSGS("age", GetAgeProp, SetAgeProp, JSPROP_ENUMERATE),
  121.         JS_PSGS("name", GetNameProp, SetNameProp, JSPROP_ENUMERATE),
  122.         JS_PS_END
  123. };
  124. static const JSFunctionSpec my_methods[] = {
  125.         JS_FS("SayHello", SayHelloFun, 0, 0),
  126.         JS_FS("SetAge", SetAgeProp, 0, 0),
  127.         JS_FS("GetAge", GetAgeProp, 0, 0),
  128.         JS_FS("SetName", SetNameProp, 0, 0),
  129.         JS_FS("GetName", GetNameProp, 0, 0),
  130.         JS_FS_END
  131. };

  132. void ExecScript(JSContext *cx)
  133. {
  134.         JSObject *obj = JS_NewGlobalObject(cx, &global_class, nullptr, FireOnNewGlobalHook);//创建一个新的JavaScript对象,用作一个全局对象,但是要被根化,这里没有被根化。
  135.         RootedObject global(cx, obj);//在当前的上下文环境,用创建的全局对象创建根化的全局对象
  136.         JSAutoCompartment ac(cx, global);//进入新全局对象的隔室,好像干什么坏事总是见不得人,非要躲在包厢里做坏事一样
  137.         JS_InitStandardClasses(cx, global);//初始化全局对象和其它js里的常规对象

  138.         JSObject* protoObj = JS_InitClass(cx, global, JS::NullPtr(), &Student_class, Constructor, 0, my_props, my_methods, nullptr, nullptr);
  139.         const char *script = "var stu=new Student('libai',888);var age=stu.GetAge();var name=stu.GetName();stu.SayHello();\
  140.                  var stu2 = new Student();stu2.SetAge(99);stu2.SetName('dupu');var age2 = stu2.GetAge(); var name2=stu2.GetName();stu2.SayHello()";
  141.         const char *filename = "noname";
  142.         int lineno = 1;
  143.         bool ok = JS_EvaluateScript(cx, global, script, strlen(script), filename, lineno);
  144.         RootedValue rval(cx);
  145.         JS_GetProperty(cx, global, "stu", &rval);
  146.         if (rval.isObject())
  147.         {
  148.                 RootedObject stuobj(cx, JSVAL_TO_OBJECT(rval));
  149.                 JS_GetProperty(cx, global, "age", &rval);
  150.                 int age = rval.toInt32();
  151.                 printf("age=%d\n", age);
  152.                 JS_GetProperty(cx, global, "name", &rval);
  153.                 JSString *str_name = rval.toString();
  154.                 printf("name=%s\n", JS_EncodeString(cx, str_name));
  155.         }
  156.         RootedValue rval2(cx);
  157.         JS_GetProperty(cx, global, "stu2", &rval2);
  158.         if (rval2.isObject())
  159.         {
  160.                 RootedObject stuobj2(cx, JSVAL_TO_OBJECT(rval2));
  161.                 JS_GetProperty(cx, global, "age2", &rval);
  162.                 int aa = rval.toInt32();
  163.                 printf("age2=%d\n", aa);
  164.                 JS_GetProperty(cx, global, "name2", &rval);
  165.                 JSString *str_name2 = rval.toString();
  166.                 printf("name2=%s\n", JS_EncodeString(cx, str_name2));
  167.         }
  168. }
  169. int _tmain(int argc, _TCHAR* argv[])
  170. {
  171.         JS_Init();     //初始化JS引擎,一定要有这步,不然其它的操作都是扯淡
  172.         JSRuntime *rt = JS_NewRuntime(8L * 1024 * 1024, JS_USE_HELPER_THREADS);//创建运行时,是不是跟以前的不一样了
  173.         if (!rt)
  174.                 return 1;
  175.         JSContext *cx = JS_NewContext(rt, 8192);//创建一个JS上下文,与运行时关联起来
  176.         if (!cx)
  177.                 return 1;
  178.         //****************************************************************************************
  179.             ExecScript(cx);
  180.         //****************************************************************************************
  181.         JS_DestroyContext(cx);//销毁创建的上下文
  182.         JS_DestroyRuntime(rt);//销毁释放运行时
  183.         JS_ShutDown();        //释放js引擎使用的所有资源
  184.         return 0;
  185. }
复制代码

程序运行结果:
SpiderMonkey_31.2.0版编程笔记 七__类操作
你可能会看到调用了两次构造函数,但是析构的时候确执行了3次,至于为什么,我也不知道,不过肯定的是构造的时候student类型p的指针地址和析构的时候p的地址是一样的,没有发生混乱,我们的注入类的操作应该是成功的。从图里你发现出现了很多没有必要的打印字符,只是为了方便观看程序运行起来之后的操作流程,你可以去掉那些打印字符,只保留有用的代码。我在帖子里不方便调试,没有事情的时候你调试一遍,你会更加深入了解运作原理。
上一篇:SpiderMonkey_31.2.0版编程笔记 六___对象操作二




上一篇:SpiderMonkey_31.2.0版编程笔记 六___对象操作二
下一篇:Directshow 中的视频捕捉,非常全的资料
14_avatar_middle
在线会员 发表于 2015-11-17 16:19:46 | 显示全部楼层
感谢楼主的无私奉献,希望能持续更新。
您需要登录后才可以回帖 登录 | 加入驿站 qq_login

本版积分规则

关闭

站长提醒上一条 /2 下一条

QQ|小黑屋|手机版|VC驿站 ( 辽ICP备09019393号tongdun|网站地图wx_jqr

GMT+8, 2019-5-19 23:37

Powered by Discuz! X3.4

© 2009-2019 cctry.com

快速回复 返回顶部 返回列表