SetCallAsFunctionHandler

和fibjs同居的日子(二) 之fibjs和v8的接口交互

一、V8引擎概览

V8是Google团队开发的高性能JavaScript开源解析引擎, 它采用c++开发并且被应用在开源的浏览器Google Chrome中。它实现了描述在-262当中的ECMAScript规范。它可以运行在WindowsXp及以上的系统, 不小于MacOsX10.5的系统, 和使用IA-32, ARM或者MIPS处理器的linux系统。V8可以独立运行,你也可以把它嵌入到你的c++应用当中去。

二、v8和自定义c++类的交互

1、基础概念

图 . V8 引擎基本概念关系图

  • handle

    handle 是指向对象的指针,在 V8 中,所有的对象都通过 handle 来引用,handle 主要用于 V8 的垃圾回收机制。

    在 V8 中,handle 分为两种:持久化 (Persistent)handle 和本地 (Local)handle,持久化 handle 存放在堆上,而本地 handle 存放在栈上。这个与 C/C++ 中的堆和栈的意义相同 ( 简而言之,堆上的空间需要开发人员自己申请,使用完成之后显式的释放;而栈上的为自动变量,在退出函数 / 方法之后自动被释放 )。持久化 handle 与本地 handle 都是 Handle 的子类。在 V8 中,所有数据访问均需要通过 handle。需要注意的是,使用持久化 handle 之后,需要显式的调用 Dispose() 来通知垃圾回收机制。

  • 作用域 (scope)

    scope 是 handle 的集合,可以包含若干个 handle,这样就无需将每个 handle 逐次释放,而是直接释放整个 scope。

    在使用本地 handle 时,需要声明一个 HandleScope 的实例,scope 是 handle 的容器,使用 scope,则无需依次释放 handle。

         
         
    1. HandleScope handle_scope;
    2. Local<ObjectTemplate> temp;
  • 上下文 (context)

    context 是一个执行器环境,使用 context 可以将相互分离的 JavaScript 脚本在同一个 V8 实例中运行,而互不干涉。在运行 JavaScript 脚本是,需要显式的指定 context 对象。
    关于javascript的handle句柄, handle scope, context等概念我就不介绍了,可以先google一下。

2、步骤

c++类暴露给javascript环境使用,一般包括以下几个步骤:

  • 1、声明类

         
         
    1. class className{
    2. public:
    3. className(){}
    4. int getProperty(){}
    5. void setProperty(int property) {}
    6. private:
    7. int property;
    8. };
  • 2、创建函数模板实例

         
         
    1. Local<FunctionTemplate> classTemplate = FunctionTemplate::New(isolate, className);
  • 3、设置类名

         
         
    1. classTemplate.SetClassName(className);
  • 4、 然后定义原型模板

         
         
    1. v8::Local<v8::ObjectTemplate> protoTemplate = classTemplate ->PrototypeTemplate();
    2. protoTemplate ->Set("getProperty", FunctionTemplate::New(ClassGetProperty));
    3. protoTemplate ->Set("setProperty", FunctionTemplate::New(ClassSetProperty));

    其中ClassGetProperty 这是函数是原型的访问器它用来包裹c++类的方法后面我们会讲到。这里的prototypetemplate对应于javascript的函数的prototype对象。所有该类创建的对象都会继承setProperty和getProperty方法。
    例如

         
         
    1. var obj = new className();
    2. obj.getProperty();
    3. obj.setProperty();

    如上代码是正确的。

  • 5、设置实例模板:

         
         
    1. v8::Local<v8::ObjectTemplate> instanceTemplate = _class->InstanceTemplate();
    2. instanceTemplate.SetInternalFieldCount(1);
  • 6、最后把函数模板实例放入对象

         
         
    1. v8::Local<v8::Object> o = v8:: Object ::new(isolate);
    2. o.set(className, classTemplate);
  • 7、编写javascript代码

         
         
    1. var class = new className();
    2. class.setProperty();
    3. class.getProperty();
3、参考资料

关于和c++的交互请参考V8 embedder's guide: https://developers.google.com/v8/embed

中文版:http://mp.weixin.qq.com/s?__biz=MzAxNDI5NzEzNg==&mid=2651156774&idx=1&sn=658c089fb5d2a21d87939000b095cd17&scene=4#wechat_redirect

这一篇博文也很有帮助
使用 Google V8 引擎开发可定制的应用程序 http://www.ibm.com/developerworks/cn/opensource/os-cn-v8engine/ 这里使用的接口参数现在已经发生了变化, 但是对理解本文还是有很大帮助的。

三、fibjs中定义的c++类是如何暴露给javascript使用的

本文如无特殊声明, c++类特指需要暴露给javascript环境使用的c++类,如fs_base、http_base等。

1、fibjs初始化。

fibjs在开始运行时会创建一个fibjs运行实例:

   
   
  1. // fibjs/src/base/fibjs.cpp
  2. Isolate* isolate = new Isolate(fname);

它对v8实例进行了封装,里面创建了fiber来执行初始化工作:

   
   
  1. // fibjs/src/base/Runtime.cpp
  2. isolate->init();

紧接着就创建了一个SandBox对象并初始化根沙箱环境:

   
   
  1. // fibjs/src/base/Runtime.cpp
  2. m_topSandbox = new SandBox();
  3. m_topSandbox->initRoot();

在initRoot方法里面我们定义的所有c++类都被安装了,即将c++类及其成员和方法暴露给javascript运行环境。

   
   
  1. // fibjs/src/sandbox/SandBox.cpp
  2. RootModule* RootModule::g_root = NULL;
  3. void SandBox::initRoot()
  4. {
  5. Isolate* isolate = holder();
  6. RootModule* pModule = RootModule::g_root;
  7. while (pModule)
  8. {
  9. ClassInfo& ci = pModule->class_info();
  10. InstallModule(ci.name(), ci.getFunction(isolate));
  11. pModule = pModule->m_next;
  12. }
  13. InstallModule("expect", createV8Function("expect", isolate->m_isolate, test_base::s_expect));
  14. }

通过RootModule来实现所有c++类的安装。

   
   
  1. // fibjs/include/object.h
  2. class RootModule
  3. {
  4. public:
  5. RootModule()
  6. {
  7. m_next = g_root;
  8. g_root = this;
  9. }
  10. public:
  11. virtual ClassInfo &class_info() = 0;
  12. public:
  13. RootModule* m_next;
  14. static RootModule* g_root;
  15. };

fibjs在每一个c++类实现的地方定义了一个RootModule的子类并重写了class_info这个虚函数, 以fs_base为例,在fs.cpp中有这样一行:

   
   
  1. DECLARE_MODULE(fs);

它是一个宏定义:

   
   
  1. // fibjs/include/utils.h
  2. #define DECLARE_MODULE(name) \
  3. class RootModule_##name : public RootModule \
  4. { \
  5. public: \
  6. virtual ClassInfo &class_info() \
  7. { \
  8. return name##_base::class_info(); \
  9. } \
  10. } s_RootModule_##name;

展开来如下所示:

   
   
  1. class RootModule_fs : public RootModule
  2. {
  3. public:
  4. virtual ClassInfo &class_info()
  5. {
  6. return name_fs::class_info();
  7. }
  8. } s_RootModule_fs;

这是一个类声明和定义, 在程序开始运行并初始化时就执行了父类的构方法。这样这个对象就被静态变量RootModule::g_root所指向了, 当main函数执行的时候, 这里就行成了一个单链表, RootModule::g_root声明为null, 通过成员m_next来指向, 使用头插法构造了一个单链表, 最终g_root指向表头。

在initRoot方法中,在while循环里取单链表里的内容执行class_info()。利用多态特性真正执行的是fs_base::class_info(), 返回了一个ClassInfo对象。

fs_base::class_info().getFunction返回了一个javascript实例也就是我们所说的c++类的v8包裹。通过InstallModule把这个实例注册到javascript对象上。

2、ClassInfo::getFunction()

CassInfo对象在每个c++类声明的时候创建:

   
   
  1. // fibjs/include/ifs/fs.h
  2. inline ClassInfo& fs_base::class_info()
  3. {
  4. static ClassData::ClassMethod s_method[] =
  5. {
  6. {"exists", s_exists, true},
  7. {"unlink", s_unlink, true},
  8. {"umask", s_umask, true},
  9. {"openTextStream", s_openTextStream, true},
  10. {"readFile", s_readFile, true},
  11. {"readLines", s_readLines, true},
  12. {"writeFile", s_writeFile, true}
  13. };
  14. static ClassData::ClassProperty s_property[] =
  15. {
  16. {"SEEK_SET", s_get_SEEK_SET, block_set, true},
  17. {"SEEK_CUR", s_get_SEEK_CUR, block_set, true},
  18. {"SEEK_END", s_get_SEEK_END, block_set, true}
  19. };
  20. static ClassData s_cd =
  21. {
  22. "fs", s__new, NULL,
  23. 15, s_method, 0, NULL, 3, s_property, NULL, NULL,
  24. NULL
  25. };
  26. //创建ClassInfo对象
  27. static ClassInfo s_ci(s_cd);
  28. return s_ci;
  29. }

其中ClassMethod、ClassProperty等类都是用来包裹c++类方法和类属性的。后面会讲到。在调用getFunction的时候把c++类的构造方法、成员变量和成员方法都按照v8提供的接口进行包装。返回javascript函数模板实例并设置到object上完成交互。

   
   
  1. // fibjs/include/ClassInfo.h
  2. v8::Local<v8::Function> getFunction(Isolate* isolate)
  3. {
  4. cache* _cache = _init(isolate);
  5. return v8::Local<v8::Function>::New(isolate->m_isolate, _cache->m_function);
  6. }

对照第二章的步骤基本可以理解fibjs的c++类和v8交互了。在ClassInfo::getFunction中是通过_init()这个方法实现的。下面讲解以注释的形式来。

   
   
  1. cache* _init(Isolate* isolate)
  2. {
  3. if (m_id < 0)
  4. m_id = (int32_t)isolate->m_classInfo.size();
  5. void* p = NULL;
  6. while ((int32_t)isolate->m_classInfo.size() < m_id + 1)
  7. isolate->m_classInfo.append(p);
  8. cache* _cache = (cache*)isolate->m_classInfo[m_id];
  9. if (_cache)
  10. return _cache;
  11. isolate->m_classInfo[m_id] = _cache = new cache();
  12. // 使用c++类构造方法创建函数模板, m_cd.cor是c++类的构造方法。
  13. v8::Local<v8::FunctionTemplate> _class = v8::FunctionTemplate::New(
  14. isolate->m_isolate, m_cd.cor);
  15. _cache->m_class.Reset(isolate->m_isolate, _class);
  16. // 给函数模板设置名称
  17. _class->SetClassName(isolate->NewFromUtf8(m_cd.name));
  18. if (m_cd.base)
  19. {
  20. // 递归查找c++的父类,并继承父类
  21. cache* _cache1 = m_cd.base->_init(isolate);
  22. _class->Inherit(
  23. v8::Local<v8::FunctionTemplate>::New(isolate->m_isolate,
  24. _cache1->m_class));
  25. }
  26. // 创建原型模板
  27. v8::Local<v8::ObjectTemplate> pt = _class->PrototypeTemplate();
  28. int32_t i;
  29. // 给原型模板设置方法, 即把c++类方法包裹器注册到原型模板上。这里的invoker就是上面fs_base::s_exists或其他方法
  30. for (i = 0; i < m_cd.mc; i++)
  31. pt->Set(isolate->NewFromUtf8(m_cd.cms[i].name),
  32. v8::FunctionTemplate::New(isolate->m_isolate, m_cd.cms[i].invoker),
  33. (v8::PropertyAttribute)(v8::ReadOnly | v8::DontDelete));
  34. // 给原型模板设置访问器, 这里是把其他c++类对象的包裹作为原型模板的对象属性。
  35. for (i = 0; i < m_cd.oc; i++)
  36. {
  37. cache* _cache1 = m_cd.cos[i].invoker()._init(isolate);
  38. pt->Set(isolate->NewFromUtf8(m_cd.cos[i].name),
  39. v8::Local<v8::FunctionTemplate>::New(isolate->m_isolate, _cache1->m_class),
  40. (v8::PropertyAttribute)(v8::ReadOnly | v8::DontDelete));
  41. }
  42. // 给原型设置属性,以及属性的访问器。这里的getter和setter就是上面的s_get_SEEK_SET和block_set。
  43. for (i = 0; i < m_cd.pc; i++)
  44. pt->SetAccessor(isolate->NewFromUtf8(m_cd.cps[i].name),
  45. m_cd.cps[i].getter, m_cd.cps[i].setter,
  46. v8::Local<v8::Value>(), v8::DEFAULT, v8::DontDelete);
  47. // 创建实例模板
  48. v8::Local<v8::ObjectTemplate> ot = _class->InstanceTemplate();
  49. ot->SetInternalFieldCount(1);
  50. ClassData *pcd;
  51. pcd = &m_cd;
  52. while (pcd && !pcd->cis)
  53. pcd = pcd->base ? &pcd->base->m_cd : NULL;
  54. // 给含有索引属性拦截器的实例模板设置拦截器,它会在访问索引属性时被调用
  55. if (pcd)
  56. ot->SetIndexedPropertyHandler(pcd->cis->getter, pcd->cis->setter);
  57. pcd = &m_cd;
  58. while (pcd && !pcd->cns)
  59. pcd = pcd->base ? &pcd->base->m_cd : NULL;
  60. // 给含有具名属性拦截器的实例模板设置拦截器,它会在访问名称为字符串的属性时被调用
  61. if (pcd)
  62. ot->SetNamedPropertyHandler(pcd->cns->getter, pcd->cns->setter,
  63. NULL, pcd->cns->remover, pcd->cns->enumerator);
  64. if (m_cd.caf)
  65. ot->SetCallAsFunctionHandler(m_cd.caf);
  66. // 调用模板的GetFunction 方法来创建一个模板的 JavaScript 实例
  67. v8::Local<v8::Function> _function = _class->GetFunction();
  68. // 设置实例的方法、属性、对象
  69. Attach(isolate, _function);
  70. _cache->m_function.Reset(isolate->m_isolate, _function);
  71. if (m_cd.cor)
  72. {
  73. v8::Local<v8::Object> o = _function->NewInstance();
  74. o->SetAlignedPointerInInternalField(0, 0);
  75. _cache->m_cache.Reset(isolate->m_isolate, o);
  76. }
  77. // 返回c++类的包裹。
  78. return _cache;
  79. }

最后看InstallModule

   
   
  1. // fibjs/src/sandbox/SandBox.cpp
  2. void SandBox::InstallModule(exlib::string fname, v8::Local<v8::Value> o)
  3. {
  4. mods()->Set(holder()->NewFromUtf8(fname), o);
  5. }

mods()

   
   
  1. v8::Local<v8::Object> mods()
  2. {
  3. Isolate* isolate = holder();
  4. v8::Local<v8::Value> v = isolate->GetPrivate(wrap(), "_mods");
  5. v8::Local<v8::Object> o;
  6. if (!v->IsUndefined())
  7. o = v->ToObject();
  8. else
  9. {
  10. o = v8::Object::New(isolate->m_isolate);
  11. isolate->SetPrivate(wrap(), "_mods", o);
  12. }
  13. return o;
  14. }

这返回的是一个v8::Local<v8::Object>对象,对应第二章步骤的第6步。完成了安装。

四、访问器对c++方法的包装

上面提到把访问器set到原型模板上。我这里以Buffer为例。解析它的slice方法是如何包裹的。
首先Buffer的用法是这样的

   
   
  1. var buf = new Buffer('hello');
  2. buf.slice(1)

从fibjs/include/ifs/buffer.h中可以看到ClassMethod、ClassProperty、ClassData这些类,它们把访问器封装起来用于统一注册到函数原型模板上。查看ClassMethod类和Buffer_base::classinfo()可以看到slice的访问器是s_slice也就是上文_init()代码中的m_cd.cms[i].invoker。它的定义是

   
   
  1. inline void Buffer_base::s_slice(const v8::FunctionCallbackInfo<v8::Value>& args)
  2. {
  3. obj_ptr<Buffer_base> vr;
  4. METHOD_INSTANCE(Buffer_base);
  5. METHOD_ENTER(1, 0);
  6. OPT_ARG(int32_t, 0, 0);
  7. hr = pInst->slice(v0, vr);
  8. METHOD_OVER(2, 2);
  9. ARG(int32_t, 0);
  10. ARG(int32_t, 1);
  11. hr = pInst->slice(v0, v1, vr);
  12. METHOD_RETURN();
  13. }

我把宏定义展开得到

可以看到通过GetArgumentValue()获取c++函数参数, pInst->slice()执行c++函数,通过args.GetReturnValue().set(V8_RETURN(GetReturnValue(isolate, vr)))来设置返回值。
而最终要的就是pInst->slice这个方法。

实现这个方法就实现了整个c++类暴露给javascript运行环境的过程。
这个方法定义在fibjs/src/global/Buffer.cpp中。

五、总结

不要担心,不要觉得复杂,以上的代码都已经被集成到工具里面通过idl文件自动生成,当我们想自定义一个fibjs模块(c++类)的时候, 我们只需要关心上文中的最后一个步骤那就是slice是怎么实现的。下一篇文章就给大家介绍如何编写自己的fibjs模块。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值