资源描述
Google v8学习笔记
Google Javascript脚本引擎v8 学习笔记
pansunyou@
2010.11.14于遵义
V0.1
目录
一、Google v8 整体印象 2
1.1 Google v8是什么,能做什么? 2
1.2 Google v8的获取及编译 2
1.2.1获取源码 2
1.2.2编译类库及示例 3
二、Google v8入门 3
2.1 v8的基本概念 3
2.2从HelloWorld开始 5
2.3 在C++中访问Javascript的变量及函数 5
2.4 在Javascript中访问C++全局函数及变量 9
2.5在C++中“声明”“Javascript类”, 供Javascript实例化 15
2.6封装完整的C++类到Javascript中,供Javascript实例化 16
三、Google v8的开源应用 16
3.1 Node.js 16
3.2 v8cgi 17
四、参考资料 17
五、本文源码 18
一、Google v8 整体印象
1.1 Google v8是什么,能做什么?
v8是一款Javascript脚本引擎,它开放源代码,由Google丹麦开发,是Google Chrome的一部分。
和其它脚本语言(php,lua,python等)一样,Javascript也可以内嵌于应用程序,和编译性语言(c/c++等)交互。由于v8是用C++写的,所以在C++中使用javascript显得更加的自然。通过在应用程序中引入脚本解析器,将一些业务逻辑交由脚本实现,可以使得系统更加灵活。C/C++的高效,Javascript的灵巧多变,使得我们的应用程序可以兼顾性能和效率。
说得再具体直白一点,就是我们可以通过编写javascript脚本来操作C++提供的类,可以编写javascript函数供C++调用。像下面这样写javascript一定非常爽吧?
var socket = new Socket(Socket.PF_INET, Socket.SOCK_STREAM, Socket.IPPROTO_TCP);
socket.setOption(Socket.SO_REUSEADDR, true);
var conn = socket.connect("", 80);
if (conn){
var httpHeader = "GET /pansunyou HTTP/1.1\r\nHost: \r\n\r\n";
socket.send(httpHeader);
var responseText = socket.receive(12000);
printf("responseText="+responseText+"\n\n");
}
socket.close();
至于其性能,好吧,算我迷信,Google出品一定差不到哪里去。再是我也未完全明白它的原理,就不抄网上的文字了,可以本文的参考资料看到相关的介绍文档。
1.2 Google v8的获取及编译
1.2.1获取源码
Google v8以源码的形式提供,其开源项目地址为:
在linux下,可以使用以下命令获取其源码:
svn checkout v8
在windows下,可以TortoiseSVN导出源码:
1.2.2编译类库及示例
Google V8的编译需要使用Scons(大概类似于Makefile或者CMake之类),而Scons需要使用Python2.x,所以需要先安装这两玩意儿。 记得python一定要是2.x系列的,否则灰常灰常的郁闷。
http://www.python.org/
http://www.scons.org/
Windows平台,进入命令后先运行,以初始化vc编译环境。
cmd /k ""C:\Program Files\Microsoft Visual Studio 9.0\VC\vcvarsall.bat"" x86
进入v8源码目录,执行Scons --help可查看编译选项。因为我是用来学习的,所以我编译debug版本,并且选择打印尽可能多的消息。在windows下,尽量选择编译为动态库吧,在为静态的v8.lib有160MB之巨,一个helloworld程序都要链接个半天。
我的编译选项:
scons mode=debug verbose=on library=shared msvcrt=static
编译结果为同目录下的v8_g.lib和v8_g.dll,release编译时无_g后缀。使用时非常简单,只需要在vc工程中加入v8的include目录,链接加入v8_g.lib即可。
二、Google v8入门
2.1 v8的基本概念
在使用v8引擎之前,先来了解一下几个基本概念:句柄(handle),作用域(scope),上下文环境(可以简单地理解为运行环境)。
2.1.1句柄(Handle)
从实质上来说,每一个句柄就是一个指向v8对象的指针,所有的v8对象必须使用句柄来操作。这是先决条件,如果一个v8对象没有任何句柄与之相关联,那么这个对象很快就会被垃圾回收器给干掉(句柄跟对象的引用计数有很大关系)。
在v8.h中,我们可以看到几个相关的类(模板?):Handle、Local、Persistent,使用时大致为:
Handle<FunctionTemplate> ft0 = FunctionTemplate::New();
Local<FunctionTemplate> ft1 = Local<FunctionTemplate>::New(FunctionTemplate::New());
Persistent<FunctionTemplate> ft3= Persistent<FunctionTemplate>::New(FunctionTemplate::New());
它们其实没有太大的差别,只是作用域不一样,Handle和Local一样会在C++的作用域中被同域的HandleScope(后面再解释)释放掉。而Persistent则相当于是永久性的实例,它所持有javascript对象只有调用Despose()的时候再会显示释放。
2.1.2作用域(Scope)
从概念上理解,作用域可以看成是一个句柄的容器,在一个作用域里面可以有很多很多个句柄(也就是说,一个scope里面可以包含很多很多个v8引擎相关的对象),句柄指向的对象是可以一个一个单独地释放的,但是很多时候(真正开始写业务代码的时候),一个一个地释放句柄过于繁琐,取而代之的是,可以释放一个scope,那么包含在这个scope中的所有handle就都会被统一释放掉了。
Scope在v8.h中有这么几个:HandleScope,Context::Scope。HandleScope是用来管理Handle的,而Context::Scope仅仅用来管理Context对象。代码像下面这样:
//在此函数中的Handle都会被handleScope管理
HandleScope handleScope;
//创建一个js执行环境Context
Handle<Context> context = Context::New();
Context::Scope contextScope(context);
//其它代码
一般情况下,函数的开始部分都放一个HandleScope,这样此函数中的Handle就不需要再理会了释放资源了。而Context::Scope仅仅做了:在构造中调用context->Enter(),而在析构函数中调用context->Leave()。
2.1.3上下文环境(Context)
从概念上讲,这个上下文环境也可以理解为运行环境。在执行javascript脚本的时候,总要有一些环境变量或者全局函数。我们如果要在自己的c++代码中嵌入v8引擎,自然希望提供一些c++编写的函数或者模块,让其他用户从脚本中直接调用,这样才会体现出javascript的强大。我们可以用c++编写全局函数或者类,让其他人通过javascript进行调用,这样,就无形中扩展了javascript的功能。
Context可以嵌套,即当前函数有一个Context,调用其它函数时如果又有一个Context,则在被调用的函数中javascript是以最近的Context为准的,当退出这个函数时,又恢复到了原来的Context。
我们可以往不同的Context里“导入”不同的全局变量及函数,互不影响。据说设计Context的最初目的是为了让浏览器在解析HTML的iframe时,让每个iframe都有独立的javascript执行环境,即一个iframe对应一个Context。
2.1.4 零散的应该预先了解的知识
2.2从HelloWorld开始
算了,HelloWorld没啥意思,就不写了。
2.3 在C++中访问Javascript的变量及函数
从Context的概念可知,无论何时我们运行javascript脚本,或者执行C++的某行代码,我们都处在当前Context(可以通过Context::GetCurrent()获取)中,即当前一定有一个javascript 运行环境。
在“当前运行环境”中存在一个全局对象,是GetCurrent()->Global(),里面的东西就是我们执行javascript脚本时可以访问的或者可以修改的全局数据。
所以,要在C++中访问Javascript脚本中的数据,就是先去执行这段javascript代码,让它去初始化或者修改或者填充Context::GetCurrent()->Global()对象,在这之后,我们就可以通过Global()对象的Get函数来提取其中的内容了,包括:全局变量,全局函数。
访问的步骤大致如下:
//从全局对象中取出需要的内容
Handle<Value> tmpVal = globalObj->Get(String::New("javascript中的全局变量名/函数名"));
//根据不同的类型做判断
tmpVal. IsEmpty(); ///< 万一javascript脚本中根据没这玩意儿
tmpVal-> IsFunction(); ///< 万一不是我们预期的函数。。。
之后便可以方便地转换为我们熟悉的V8类型了,比如String,Int32,Function,Array等等。
源码之下无秘密(辅助性的函数参考本文尾部源码包):fromCppToJavaScript.js:
var Config = {
testString:"HelloWorld",
testInt: 1234,
testFunc:function(hello){
printf("testFunc:"+hello+"\n");
}
}
var globalStr = "hi, I am globalStr.";
var globalInt = -12345;
var globalFunction = function(a,b){
return a+b;
}
printf("hello from fromCppToJavaScript.js !\n");
///
/// @brief 学习如何在c++中访问javascript脚本中的变量及函数
/// @param argv[1] 演示用的js
int fromCppToJavaScript(int argc, char**argv)
{
if (argc!=2)
{
printf("参数错误! x.exe fromCppToJavaScript.js\n");
return 0;
}
HandleScope handleScope;
Handle<Context> context = Context::New();
Context::Scope contextScope(context);
//javascript中无打印函数
Handle<Object> globalObj = Context::GetCurrent()->Global();
globalObj->Set(String::New("printf"), FunctionTemplate::New(_printfForJS)->GetFunction());
string scriptFileText = tk::getFileContents(argv[1]);
if (scriptFileText.length()<=0)
{
printf("无法打开js文件!\n");
return 0;
}
Handle<String> scriptSource = String::New(scriptFileText.c_str());
//"编译"脚本
TryCatch catcher;
Handle<Script> script = Script::Compile(scriptSource);
if (catcher.HasCaught()) {
tk::printError(&catcher);
return 0;
}
//执行脚本
Handle<Value> result = script->Run();
if (catcher.HasCaught()) {
tk::printError(&catcher);
return 0;
}
//Run()之后便可以访问script中的变量及函数了
//1. 访问全局String变量 globalStr
Handle<Value> _globalStr = globalObj->Get(String::New("globalStr"));
if (_globalStr.IsEmpty())
printf("脚本中无globalStr变量!\n");
else
printf("globalStr=%s\n", *String::Utf8Value(_globalStr));
//2. 全局整型变量 globalInt
Handle<Value> _globalInt = globalObj->Get(String::New("globalInt"));
if (_globalInt.IsEmpty())
printf("脚本中无globalInt变量!\n");
else
{
printf("globalInt=%d\n", _globalInt->ToInt32()->Value());
}
//3. 调用全局函数globalFunction
Handle<Value> _globalFunction = globalObj->Get(String::New("globalFunction"));
if (_globalFunction.IsEmpty()||!_globalFunction->IsFunction())
printf("脚本中无globalFunction函数!\n");
else
{
Local<Value> v1 = Int32::New(123);
Local<Value> v2 = Int32::New(456);
Handle<Function> func = Handle<Function>::Cast(_globalFunction);
Handle<Value> args[2] = { v1, v2 };
Handle<Value> tmpCallVal = func->Call(globalObj, 2, args);
if (tmpCallVal->IsInt32())
printf("globalFunction(%d,%d)=%d\n", v1->ToInt32()->Value(), v2->ToInt32()->Value(),
tmpCallVal->ToInt32()->Value());
else
printf("Error: globalFunction返回的应该是int才对...\n");
}
//4. 访问全局对象Config
Handle<Value> _Config = globalObj->Get(String::New("Config"));
if (_Config.IsEmpty())
printf("脚本中无Config变量!\n");
else
{
if (_Config->IsObject()){
//4.1 对象中的成员变量 Config.testString
Handle<Value> _testString = Handle<Object>::Cast(_Config)->Get(String::New("testString"));
if (!_testString.IsEmpty())
printf("Config.testString=%s\n", *String::Utf8Value(_testString));
//4.2 对象中的函数Config.testFunc
Handle<Value> _testFunc = Handle<Object>::Cast(_Config)->Get(String::New("testFunc"));
if (!_testFunc->IsFunction())
printf("Config中无_testFunc函数!\n");
else
{
Handle<Function> ConfigTestFun = Handle<Function>::Cast(_testFunc);
Handle<Value> args[] = {String::New("callJavascriptFunctionFromCpp\n")};
ConfigTestFun->Call(globalObj,1,args);
}
}
}
return 0;
}
2.4 在Javascript中访问C++全局函数及变量
V8是javascript引擎,凡javascript中有的概念,都有C++相对应的类或者模板类与之对应。Javascript中无非就是function,object,String,Array,Date,Number这些,与之对应的C++类有Function/FunctionTemplate、Object/ObjectTemplate、String、Array、Date等。
我们知道当我们通过Script的Run去执行javascript代码时,我们的代码相当于处在一个全局对象内:Context::GetCurrent()->Global(),所以我们要让javascript访问到我们在C++中捏造出的东西,只要向Global()对象注册各种数据即可,调用函数为Set。
测试用的js脚本fromJavaScriptToCpp.js
var test = {
dumpArray:function()
{
printf("[fromJavaScriptToCpp::dumpArray]\n");
//测试访问全局变量testArray
var _type = typeof(testArray);
var len = testArray.length;
printf("testArray.length="+len+"\n");
printf("typeof(testArray)="+_type+"\n");
testArray.push("another string.....");
printf("printf(testArray.join('|'))="+testArray.join("|"));
printf("\n\n");
},
dumpObject:function()
{
printf("[fromJavaScriptToCpp::dumpObject]\n");
//测试访问全局变量testObject
printf("testObject.strVal="+testObject.strVal+"\n");
printf("\n");
},
testSocket:function()
{
printf("[fromJavaScriptToCpp::testSocket]\n");
//将socket移植到javascript中
var socket = new Socket(Socket.PF_INET, Socket.SOCK_STREAM, Socket.IPPROTO_TCP);
socket.setOption(Socket.SO_REUSEADDR, true);
var conn = socket.connect("", 80);
if (conn)
{
//
var httpHeader = "GET /pansunyou HTTP/1.1\r\nHost: \r\n\r\n";
socket.send(httpHeader);
var responseText = socket.receive(12000);
printf("responseText="+responseText+"\n\n");
}
socket.close();
},
testTestObj:function()
{
printf("[fromJavaScriptToCpp::testTestObj]\n");
var obj = new TestObj;
obj.printName();
obj.setName("pan");
obj.printName();
obj.setName("pansun");
obj.printName();
obj.setName("pansunyou");
obj.printName();
obj.dispose();
}
}
printf("fromJavaScriptToCpp.js\n");
test.dumpArray();
test.dumpObject();
//test.testSocket();
test.testTestObj();
C++代码 fromJavaScriptToCpp.cpp:
//仅供测试
class CTestObj {
public:
CTestObj(){
printf("CTestObj::CTestObj\n");
buffer = (char*)malloc(1024*1024);
}
~CTestObj(){
printf("CTestObj::~CTestObj\n");
if(buffer){
free(buffer);
buffer = NULL;
}
}
void printfName(){
printf("CTestObj::printfName name=%s\n", name.c_str());
}
void setName(const char*xname){
name = xname;
}
string name;
char *buffer;
};
//此函数用来做为TestObj的"构造函数"
//在 var obj = new TestObj; 的时候调用
JS_METHOD(_testObjConstructor)
{
ASSERT_CONSTRUCTOR;
SAVE_PTR(0, new CTestObj());
return args.This();
}
//只是个包装
JS_METHOD(_testObjPrintName)
{
CTestObj *pObj = LOAD_PTR(0, CTestObj *);
if(pObj!=NULL){
pObj->printfName();
}
return args.This();
}
//只是个包装
JS_METHOD(_testObjSetName)
{
CTestObj *pObj = LOAD_PTR(0, CTestObj *);
if(pObj!=NULL){
String::AsciiValue xname(args[0]);
pObj->setName(*xname);
}
return args.This();
}
//用来显式地释放内部数据
//obj.dispose();
JS_METHOD(_testObjDispose)
{
CTestObj *pObj = LOAD_PTR(0, CTestObj *);
if(pObj!=NULL){
delete pObj;
pObj = NULL;
SAVE_PTR(0, pObj);
}
return args.This();
}
///
/// @brief 在javascript环境中构造一个class TestObj
///
/// @confused v8的垃圾回收机制还没弄明白, 这里的TestObj必须显式释放: obj.dispose();
///
Handle<v8::FunctionTemplate> pubishCppClass_TestObj()
{
v8::HandleScope handle_scope;
//指定"构造函数"
v8::Handle<v8::FunctionTemplate> ft = v8::FunctionTemplate::New(_testObjConstructor);
ft->SetClassName(JS_STR("TestObj"));
//在javascript的class内放一个C++实例, *CTestObj
//所以要设定InternalFieldCount为1
v8::Handle<v8::ObjectTemplate> it = ft->InstanceTemplate();
it->SetInternalFieldCount(1); /* *CTestObj */
//类的"成员函数"
v8::Handle<v8::ObjectTemplate> pt = ft->PrototypeTemplate();
pt->Set("printName", v8::FunctionTemplate::New(_testObjPrintName));
pt->Set("setName", v8::FunctionTemplate::New(_testObjSetName));
//用来释放内部数据...
pt->Set("dispose", v8::FunctionTemplate::New(_testObjDispose));
return handle_scope.Close(ft);
}
extern Handle<v8::FunctionTemplate> pubishJavascriptClass_Socket();
///
/// @brief 学习如何在javascript中访问c++中的变量及函数
/// @param argv[1] 演示用的js
int fromJavaScriptToCpp(int argc, char**argv)
{
if (argc!=2)
{
printf("参数错误! x.exe fromJavaScriptToCpp.js\n");
return 0;
}
HandleScope handleScope;
Handle<Context> context = Context::New();
Context::Scope contextScope(context);
//javascript中无打印函数
Handle<Object> globalObj = Context::GetCurrent()->Global();
globalObj->Set(String::New("printf"), FunctionTemplate::New(_printfForJS)->GetFunction());
string scriptFileText = tk::getFileContents(argv[1]);
if (scriptFileText.length()<=0)
{
printf("无法打开js文件!\n");
return 0;
}
//为脚本构造一个执行环境
//1. 创建一个字符串数组(当然也可以是其它类型)
Handle<Array> _array = Array::New();
char *strList[] = {"hello","the", "world"};
for (int i=0; i<sizeof(strList)/sizeof(strList[0]); i++)
_array->Set(JS_INT(i), JS_STR(strList[i]));
globalObj->Set(String::New("testArray"), _array);
//2. 创建一个Object testObject
Handle<Object> _obj = Object::New();
_obj->Set(JS_STR("strVal"), JS_STR("Hi,this is strVal"));
globalObj->Set(String::New("testObject"), _obj);
//3. 创建一个可以在javascript中New的class...(怎么表述?)
Handle<v8::FunctionTemplate> bb = pubishJavascriptClass_Socket();
globalObj->Set(String::New("Socket"), bb->GetFunction());
//4. 包装一个含有真正C++类实体的javascript class
//@confused v8的垃圾回收机制还没弄明白, 这里的TestObj必须显式释放: obj.dispose();
Handle<v8::FunctionTemplate> cc = pubishCppClass_TestObj();
globalObj->Set(String::New("TestObj"), cc->GetFunction());
//还有其它一些东西,比如SetAccessor
//有空再研究
//globalObj->SetAccessor()
//最后,环境准备好后,可以执行脚本了.
Handle<String> scriptSource = String::New(scriptFileText.c_str());
//"编译"脚本
TryCatch catcher;
Handle<Script> script = Script::Compile(scriptSource);
if (catcher.HasCaught()) {
tk::printError(&catcher);
return 0;
}
//执行脚本
Handle<Value> result = script->Run();
if (catcher.HasCaught()) {
tk::printError(&catcher);
return 0;
}
///@confused 这样清理有用么?
v8::Handle<v8::Array> keys = JS_GLOBAL->GetPropertyNames();
int length = keys->Length();
for (int i=0;i<length;i++) {
v8::Handle<v8::String> key = keys->Get(JS_INT(i))->ToString();
JS_GLOBAL->ForceDelete(key);
}
return 0;
}
2.5在C++中“声明”“Javascript类”, 供Javascript实例化
其实2.4中已经在使用了,这里单独总结一下。
我的初衷是想能够像这样写javascript代码:
var o
展开阅读全文