北京理工大学 20981 陈罡
一、写在前面的话
上回写到了关于如何在c++的代码中嵌入v8引擎,实现javascript脚本中调用c++的函数以及从c++函数中调用javascript脚本中定义的函数(这其实就是设计模式中所谓的reflection,反射机制的具体实现)。通过了了几行代码就可以实现如此复杂的反射机制,由此可见v8引擎带来了很大的便利性。但是上回偶只探索了一下实现c++与javascript脚本之间的函数相互调用,那么如何通过javascript调用c++的对象呢?按理说,javascript与c++的对象一定是应该可以互相调用的,但在实际的应用过程中,偶发现通过c++去调用javascript中定义的对象,似乎用得不那么多(主要是效率问题),而从javascript调用c++中已经定义好的对象,则是非常方便的,可以利用c++实现一些高效率的对象,然后由javascript进行调用,从而兼顾了效率与灵活性两个方面。
对于c++代码中如何为javascript导出一个对象供其调用,偶在网上google了一下这个问题。比较有名的解释就是另外一个叫做cproxyv8的开源项目。该项目通过几个代理类可以很方便地将c++中的类导出到javascript空间中,例如,如果开发者导出了一个叫做CPoint的类,那么在javascript脚本中可以这样去使用:
var pt=new CPoint();
看上去貌似非常方便,但是偶查了一下cproxy的known issues,里面明确说明了通过cproxyv8导出的c++类是存在“内部释放”(native disposed)的问题的。
例如,如果偶这样去编写javascipt脚本:
r = new Rect() ;
pt = r.topleft() ;
pt.x = 3 ;
pt.y = 4 ;
// 隔一大段代码。。。
pt.x = 0 ; // 注意,这里或许就会出问题
py.y = 10 ;
这一段脚本,看上去是没有问题的,r是Rect类的对象(Rect类是从c++中通过cproxyv8导出到javascript中的一个类),那么当调用r.topleft()的时候,就会返回一个Point对象,该对象是r对象的组成部分(Rect类是由topleft和rightbottom两个Point对象组合而成的),然后,偶们就可以使用pt对象对topleft的x,y坐标进行操作。
但是现在问题出现了,如果在后续javascript脚本中没有任何代码引用到r对象,那么对于v8引擎来说,这个r对象是可以释放掉的,一旦r被释放掉了,那么其内部的topleft和rightbottom两个Point对象自然也会被释放掉,如此以来,pt变量引用的r.topleft()对象也被释放掉了。此时,如果再对pt进行存取操作,就会引起错误和异常。这也就是所谓的“内部释放”问题了。当然,如果这里的Rect类是在javascript脚本中定义的话,则不会出现这种问题。
偶个人以为,如果真的需要通过javascript操作和使用c++的对象,还是直接使用全局对象比较好。由于全局对象在javascript脚本执行的整个生命周期内都是可以访问的,因此,不会出现上述的内部释放问题。另外,对于cproxyv8,或许是偶钻研得不够深入,在最新版本的v8引擎上似乎不可以工作,还有就是一旦使用过程中出现了问题,编译器会输出让人头昏眼花的错误提示信息,如果不仔细地一条条地追踪,就会变得根本不知所云。因此,偶还是决定直接使用v8引擎中提供的类和函数来导出全局的c++对象,而不是通过代理类来解决问题(当然,不可否认cproxyv8确实提供了极大的便利性!至于用不用这个cproxyv8,那完全是萝卜白菜各有所好了)。
二、v8引擎导出c++类的方法
1、准备工作
要想为javascript脚本导出一个全局的c++对象,那至少应该定义一个c++的类吧?!
下面就是偶作实验的时候声明的一个c++类(偶把它的名字姑且定为CFoo吧,以下是.h文件的定义):
#ifndef _FOO_H_
#define _FOO_H_
#include
#include "canvas.h"
class CFoo {
public:
CFoo(CCanvas * canv_ptr) ;
~CFoo() ;
private:
CCanvas * m_canv_ptr ; // 这里是保存外部的一个绘图对象的指针,把真正的绘图工作交给它做好了。
public: // member functions
inline void SetColor(int r, int g, int b) {
if(m_canv_ptr) {
m_canv_ptr->SetDrawColor(r, g, b) ;
}
}
inline void Line(int x1, int y1, int x2, int y2) {
if(m_canv_ptr) {
m_canv_ptr->DrawLine(x1, y1, x2, y2) ;
}
}
inline void Commit() {
if(m_canv_ptr) {
m_canv_ptr->Commit() ;
}
}
public: // 下面这些方法必须定义成static类型的,用于给javascript脚本调用的。
static v8::Handle set_color(const v8::Arguments & args) ;
static v8::Handle line(const v8::Arguments & args) ;
static v8::Handle commit(const v8::Arguments & args) ;
} ;
#endif
再往下是具体的实现,里面有一些奇怪的函数,稍后偶再来讲解(以下是.cpp文件):
CFoo::CFoo(CCanvas * canv_ptr):m_canv_ptr(canv_ptr) {}
CFoo::~CFoo() { }
v8::Handle CFoo::set_color(const v8::Arguments& args) {
CFoo * foo_ptr = util_unwrap_obj(args.Holder()) ; // 这里的util_unwrap_obj稍后再给出其定义
if(args.Length() == 3) {
foo_ptr->SetColor(args[0]->Int32Value(),
args[1]->Int32Value(),
args[2]->Int32Value()) ;
}
return v8::Undefined() ;
}
v8::Handle CFoo::line(const v8::Arguments& args) {
CFoo * foo_ptr = util_unwrap_obj(args.Holder()) ;
if(args.Length() == 4) {
foo_ptr->Line(args[0]->Int32Value(),
args[1]->Int32Value(),
args[2]->Int32Value(),
args[3]->Int32Value()) ;
}
return v8::Undefined() ;
}
v8::Handle CFoo::commit(const v8::Arguments& args) {
CFoo * foo_ptr = util_unwrap_obj(args.Holder()) ;
foo_ptr->Commit() ;
return v8::Undefined() ;
}
2、创建一个CFoo全局指针,然后在初始化的时候创建其对象
static CFoo * g_foo_ptr ;
......
int main(int argc, char * argv[]) {
...
try {
g_foo_ptr = new CFoo(g_canv_ptr) ;
} catch(...) {
LOG("main, new CFoo object failed!") ;
delete g_canv_ptr ;
goto END_MAIN ;
}
...
return 0 ;
}
3、初始化javascript执行环境的时候,加入该全局对象(看上去似乎很复杂,但仔细看来,实际上非常简单):
static void on_click(const char * js_fname, int x, int y) {
const int argc = 2 ;
HandleScope handle_scope ;
Handle global_templ ;
Handle foo_templ ;
Handle foo_class_ptr ;
Handle
阅读(11753) | 评论(1) | 转发(1) |