JavaScript是一种广泛用于Web客户端开发的脚本语言,常用来控制浏览器的DOM树,给HTML网页添加动态功能。目前JavaScript遵循的web标准的是ECMAScript262。由于JavaScript提供了丰富的内置函数、良好的对象机制。所以JavaScript还可以嵌入到某一种宿主语言中,弥补宿主语言的表现力,从而实现快速、灵活、可定制的开发。
现有的主流浏览器基本上都实现了一个自己的JavaScript引擎。这些JavaScript引擎可以分析、编译和执行JavaScript脚本。这些JavaScript引擎都是用C或者C++语言写的,都对外提供了API接口。所以在C、C++语言中使用这些JavaScript引擎,嵌入JavaScript是非常方便的。有一些著名的开源项目都使用了这一种方式,来进行混合的编程,比如Node.js, K-3D等。
已知著名的JavaScript引擎有Google的V8引擎、IE的Trident引擎、Firefox的SpiderMonkey引擎、Webkit的JavaScriptCore引擎、Opera的Carakan引擎(非开源的,本文没有分析)等。这些JavaScript引擎对外提供的API接口在细节上各不相同,但是这些API的一个基本的设计思路都类似。C、C++要使用这些引擎,首先要获得一个全局的Global对象。这个全局的Global对象有属性、方法、事件。比如在JavaScript环境中有一个window窗口对象。它描述的是一个浏览器窗口。一般JavaScript要引用它的属性和方法时,不需要用“window.xxx”这种形式,而直接使用“xxx”。 它是JavaScript中最大的对象,所有的其他JavaScript对象、函数或者是它的子对象,或者是子对象的子对象。C、C++通过对这个最大的Global对象调用get、set操作就可以实现与JavaScript进行双向交互了。
下面的介绍涉及到比较多的代码细节,先给个结论吧,不想看C++细节代码可以不看了。
?
编写语言
API接口
C、C++与JavaScript交互(变量、函数、类)
windows xp
vc2005编译
静态库的大小
示例EXE的大小
执行、解析JavaScript的速度
Google V8
C++
C++
可以
23.1M
1.1M
最快
Firefox3.5以前 SpiderMonkey
C
C
可以
1.3M
500K
慢
Firefox高版本SpiderMonkey
C++
C
可以
15.3M
1.7M
一般
Webkit? JavaScriptCore
C++
C
可以
26.2M
1.4M
一般
IE
未知
COM
可以
未知
100K(没有链接库)
一般
如果优先考虑库的体积,建议使用Firefox的老版本。对执行效率有要求的话,建议使用V8。
Google Chrome是google 2008年9月发布的浏览器,Chrome的网页渲染部分使用的是Webkit的渲染引擎,Chrome的JavaScript引擎就是大名鼎鼎的V8了。V8是C++语言编写的,是开放源码的,是所有的JavaScript引擎中速度最块的。其开源项目地址为:http://code.google.com/p/v8。
V8对外的API接口是C++的接口。V8的API定义了几个基本概念:句柄(handle),作用域(scope),上下文环境(Context)。模板(Templates),了解这些基本的概念才可以使用V8。
l??上下文环境Context就是脚本的运行环境,JavaScript的变量、函数等都存在于上下文环境Context中。Context可以嵌套,即当前函数有一个Context,调用其它函数时如果又有一个Context,则在被调用的函数中javascript是以最近的Context为准的,当退出这个函数时,又恢复到了原来的Context。
l??句柄(handle)就是一个指向V8对象的指针,有点像C++的智能指针。所有的v8对象必须使用句柄来操作。没有句柄指向的V8对象,很快会被垃圾回收器回收了。
l??作用域(scope)是句柄的容器,一个作用域(scope)可以有很多句柄(handle)。当离开一个作用域(scope)时,所有在作用域(scope)里的句柄(handle)都会被释放了。
l??模板(Templates)分为函数模板和对象模板,是V8对JavaScript的函数和对象的封装。方便C++语言操作JavaScript的函数和对象。
l??V8 API定义了一组类或者模板,用来与JavaScript的语言概念一一对应。比如:
V8的 Function模板与JavaScript的函数对应
V8的Object类与JavaScript的对象对应
V8的String类与JavaScript的字符对应
V8的Script类与JavaScript的脚本文本对应,它可以编译并执行一段脚本。
使用V8,在C++中访问Javascript脚本中的内容,首先要调用Context::GetCurrent()->Global()获取到Global全局对象,再通过Global全局对象的Get函数来提取Javascript的全局变量、全局函数、全局复杂对象。C++代码示例如下:
//获取Global对象
Handle<Object>globalObj = Context::GetCurrent()->Global();
?
//获取Javascrip全局变量
Handle<Value>value = globalObj->Get(String::New("JavaScript变量名"));
intn = value?->ToInt32()->Value();
?
//获取Javascrip全局函数,并调用全局函数
Handle<Value>value = globalObj->Get(String::New("JavaScript函数名"));
Handle<Function>?func?=?Handle<Function>::Cast(value) ;//转换为函数
Local<Value>?v1?=?Int32::New(0);
????Local<Value>?v2?=?Int32::New(1);
Handle<Value>?args[2]?=?{?v1,?v2?}; //函数参数
func->Call(globalObj,?2,?args);
?
//获取Javascrip全局对象,并调用对象的函数
Handle<Value>value = globalObj->Get(String::New("JavaScript复杂对象名"));
Handle<Object>?obj?=?Handle<Object>::Cast(value);//转换为复杂对象
Handle<Value>?objFunc?=?obj?->Get(String::New("JavaScript对象函数名"));
Handle<Value>?args[]?=?{String::New("callobject function ")};//函数参数
objFunc->Call(globalObj,?1,?args);
使用V8,在Javascript脚本中想要访问C++中的内容,必须先将C++的变量、函数、类注入到Javacsript中。注入时,首先要调用Context::GetCurrent()->Global()获取到Global对象,再通过Global对象的Set函数来注入全局变量、全局函数、类对象。
全局变量、全局函数的注入过程与上一节的代码类似,这里就省略不写了。比较麻烦的是将C++的类、类对象注入到Javascript中。其基本的过程如下:
1.???首先定义一个C++类,定义一个类对象指针
2.??定义一组C++全局函数,封装V8对C++类的调用,提供给V8进行CALLBACK回调。
3.???最后调用V8 API,将定义的C++类和C++函数注入到Javascript中
?
在V8的API接口中,还提供了一个“内部数据”(Internal Field)的概念,“内部数据”就是允许V8对象保存一个C++的void*指针。当V8回调C++全局函数时,C++可以设置或者获取该void*指针。
?
下面是一个将C++类、类变量注入到Javascript中的C++代码示例。
//一个C++类test、
class test
{
public:
??? test(){number=0;};
??? voidfunc(){number++;}
??? int number;
};
//一个全局对象g_test
//目的是:在Javascript中可以直接使用这个对象,例如g_test.func()
test g_test;
?
//封装V8调用test类构造函数
//在Javascript中如果执行:var t = new test;V8就会调用这个C++函数
//在C++中执行NewInstance函数注入对象时,也会调用这个函数
//默认的test类的构造函数没有参数,C++注入对象时,提供一个额外的参数
v8::Handle<v8::Value> testConstructor(constv8::Arguments& args)
{
??? v8::Local<v8::Object>self = args.Holder();
??? //这里假定有两个“内部数据”(Internal Field)
??? //第一个“内部数据”保存test对象的指针
??? //第二个“内部数据”为1 就表示这个对象是由C++注入的
??? //第二个“内部数据”为0 就表示这个对象是JS中自己建立的
??? if(args.Length())
??? {
??????? //默认为0,当C++注入对象时,会填充这个“内部数据”
??????? self->SetInternalField(0,v8::External::New(0));
??????? self->SetInternalField(1,v8::Int32::New(1));
??? }
??? else
??? {
??????? self->SetInternalField(0,v8::External::New(new test));
??????? self->SetInternalField(1,v8::Int32::New(0));
??? }
??? return self;
}
?
//封装V8调用test类func方法
//在Javascript中如果执行:t.func();V8就会调用这个C++函数
v8::Handle<v8::Value> testFunc(constv8::Arguments& args)
{
??? //获取构造函数testConstructor时,设置的对象指针
??? v8::Local<v8::Object>self = args.Holder();
v8::Local<v8::External> wrap =v8::Local<v8::External>::Cast(self->GetInternalField(0));
??? void* ptr =wrap->Value();
??? //调用类方法
??? static_cast<test*>(ptr)->func();
??? returnv8::Undefined();
}
?
//封装V8调用test类成员变量number
//在Javascript中如果执行:t.number;V8就会调用这个C++函数
v8::Handle<v8::Value>getTestNumber(v8::Local<v8::String> property, const v8::AccessorInfo& info)
{
??? //获取构造函数testConstructor时,设置的对象指针
??? v8::Local<v8::Object>self = info.Holder();
v8::Local<v8::External> wrap =v8::Local<v8::External>::Cast(self->GetInternalField(0));
??? void* ptr =wrap->Value();
??? //返回类变量
??? returnv8::Int32::New(static_cast<test*>(ptr)->number);
}
?
//C++类和全局的函数定义好以后,就可以开始将类、变量注入V8 Javascript中了
//获取global对象
v8::Handle<v8::Object> globalObj =context->Global();
?
//新建一个函数模板,testConstructor是上面定义的全局函数
v8::Handle<v8::FunctionTemplate> test_templ =v8::FunctionTemplate::New(testConstructor);
?
//设置类名称
test_templ->SetClassName(v8::String::New("test"));
?
//获取Prototype,
v8::Handle<v8::ObjectTemplate> test_proto =test_templ->PrototypeTemplate();
?
//增加类成员函数,testFunc是上面定义的全局函数
test_proto->Set("func",v8::FunctionTemplate::New(testFunc));
?
//设置两个内部数据,用于构造函数testConstructor时,存放类对象的指针。
v8::Handle<v8::ObjectTemplate> test_inst =test_templ->InstanceTemplate();
test_inst->SetInternalFieldCount(2)
?
//增加类成员变量
//getTestNumber是上面定义的全局函数
//只提供了成员变量的get操作,最后一个参数是成员变量的set操作,这里省略了
test_inst->SetAccessor(v8::String::New("number"),getTestNumber, 0);
?
//将test类的定义注入到Javascript中
v8::Handle<v8::Function> point_ctor =test_templ->GetFunction();
globalObj->Set(v8::String::New("test"),point_ctor);
?
//新建一个test对象,并使得g_test绑定新建的对象
v8::Handle<v8::Value> flag = v8::Int32::New(1);
v8::Handle<v8::Object> obj =point_ctor->NewInstance(1, &flag);
obj->SetInternalField(0, v8::External::New(&g_test));
globalObj->Set(v8::String::New("g_test"), obj);
?
将C++类和类指针注入到V8 JavaScript后,在JavaScript中就可以这样使用了:
g_test.func();
var n = g_test.number;
var t = new test;