V8 引擎使用示例
使用 C++变量
在 JavaScript 与 V8 间共享变量事实上是非常容易的,基本模板如下:
static type xxx;
static Handle<Value> xxxGetter(
Local<String> name,
const AccessorInfo& info){
//code about get xxx
}
static void xxxSetter( Local<String> name,Local<Value> value,const AccessorInfo& info){
//code about set xxx
}
调用 C++函数
在 JavaScript 中调用 C++函数是脚本化最常见的方式,通过使用 C++函数,可以极大程度的增强 JavaScript 脚本的能力,如文件读写,网络/数据库访问,图形/图像处理等等, 而在 V8 中,调用 C++函数也非常的方便。
我们来看一下v8引擎的使用,首先看一个v8版的HelloWorld:
#include <v8.h>
using namespace v8;
int main(int argc, char *argv[]) {
// 创建一个句柄作用域(在栈上)
HandleScope handle_scope;
// 创建一个新的上下文对象
Persistent<Context> context = Context::New();
// 进入上一步创建的上下文,用于编译执行helloworld
Context::Scope context_scope(context);
// 创建一个字符串对象,值为'Hello, Wrold!', 字符串对象被JS引擎
// 求值后,结果为'Hello, World!'
Handle<String> source = String::New("'Hello' + ', World!'");
// 执行脚本,获取结果
Handle <Value> result = script->Run();
// 释放上下文资源
context.Dispose();
// 转换结果为字符串
String::AsciiValue ascii(result);
printf("%s\n", *ascii);
return 0;
}
首先在 C++中定义数据,并以约定的方式定义 getter/setter 函数,然后需要将 getter/setter 通过下列机制公开给脚本:
global->SetAccessor(String::New("xxx"), xxxGetter, xxxSetter);
其中,global 对象为一个全局对象的模板:
Handle<ObjectTemplate> global = ObjectTemplate::New();
下面我们来看一个实例:
static char sname[512] = {0};
static Handle<Value> NameGetter(Local<String> name,
const AccessorInfo& info) {
return String::New((char*)&sname,strlen((char*)&sname));
}
static void NameSetter(Local<String> name, Local<Value> value,const AccessorInfo& info) {
Local<String> str = value->ToString();
str->WriteAscii((char*)&sname);
}
定义了 NameGetter, NameSetter 之后,在 main 函数中,将其注册在 global 上:
// Create a template for the global object.
Handle<ObjectTemplate> global = ObjectTemplate::New();
//public the name variable to script
global->SetAccessor(String::New("name"), NameGetter, NameSetter);
在 C++中,将 sname 的值设置为”cpp”:
//set sname to "cpp" in cpp program
strncpy(sname, "cpp", sizeof(sname));
然后在 JavaScript 中访问该变量,并修改:
print(name);
//set the variable `name` to "js"
name='js';
print(name);
运行结果如下:
cpp js
在 C++代码中,定义以下原型的函数:
Handle<Value> function(const Arguments& args){
//return something
}
然后,再将其公开给脚本:
global->Set(String::New("function"),FunctionTemplate::New(function));
同样,我们来看两个示例:
Handle<Value> Add(const Arguments& args){
int a = args[0]->Uint32Value();
int b = args[1]->Uint32Value();
return Integer::New(a+b);
}
Handle<Value> Print(const Arguments& args) {
bool first = true;
for (int i = 0; i < args.Length(); i++) {
HandleScope handle_scope;
if (first) {
first = false;
} else {
printf(" ");
}
String::Utf8Value str(args[i]);
const char* cstr = ToCString(str);
printf("%s", cstr);
}
printf("\n");
fflush(stdout);
return Undefined();
}
函数 Add 将两个参数相加,并返回和。函数 Print 接受任意多个参数,然后将参数转换为字符串输出,最后输出换行。
global->Set(String::New("print"), FunctionTemplate::New(Print));
global->Set(String::New("add"), FunctionTemplate::New(Add));
我们定义以下脚本:
var x = (function(a, b){
return a + b;
})(12, 7);
print(x);
//invoke function add defined in cpp
var y = add(43, 9);
print(y);
运行结果如下:
19 52
使用 C++类
如果从面向对象的视角来分析,最合理的方式是将 C++类公开给 JavaScript,这样可以将 JavaScript 内置的对象数量大大增加,从而尽可能少的使用宿主语言,而更大的利用动态语言的灵活性和扩展性。事实上,C++语言概念众多,内容繁复,学习曲线较 JavaScript 远为陡峭。最好的应用场景是:既有脚本语言的灵活性,又有 C/C++等系统语言的效率。 使用 V8 引擎,可以很方便的将 C++类”包装”成可供 JavaScript 使用的资源。
我们这里举一个较为简单的例子,定义一个 Person 类,然后将这个类包装并暴露给 JavaScript 脚本,在脚本中新建 Person 类的对象,使用 Person 对象的方法。
首先,我们在 C++中定义好类 Person:
class Person {
private:unsigned int age;
char name[512];
public:Person(unsigned int age, char *name) {
this->age = age;
strncpy(this->name, name, sizeof(this->name));
}
unsigned int getAge() {
return this->age;
}
void setAge(unsigned int nage) {
this->age = nage;
}
char *getName() {
return this->name;
}
void setName(char *nname) {
strncpy(this->name, nname, sizeof(this->name));
}
};
Person
类的结构很简单,只包含两个字段 age
和 name
,并定义了各自的 getter/setter
. 然后我们来定义构造器的包装:
Handle<Value> PersonConstructor(const Arguments& args){
Handle<Object> object = args.This();
HandleScope handle_scope;
int age = args[0]->Uint32Value();
String::Utf8Value str(args[1]);
char* name = ToCString(str);
Person *person = new Person(age, name);
object->SetInternalField(0, External::New(person));
return object;
}
从函数原型上可以看出,构造器的包装与上一小节中,函数的包装是一致的,因为构造函数 在 V8 看来,也是一个函数。需要注意的是,从 args 中获取参数并转换为合适的类型之后, 我们根据此参数来调用 Person
类实际的构造函数,并将其设置在 object
的内部字段中。 紧接着,我们需要包装 Person
类的 getter/setter
:
Handle<Value> PersonGetAge(const Arguments& args){
Local<Object> self = args.Holder(); Local<External> wrap =
Local<External>::Cast(self->GetInternalField(0));
void *ptr = wrap->Value();
return Integer::New(static_cast<Person*>(ptr)->getAge()); }
Handle<Value> PersonSetAge(const Arguments& args)
{
Local<Object> self = args.Holder();
Local<External> wrap = Local<External>::Cast(self->GetInternalField(0));
void* ptr = wrap->Value();
static_cast<Person*>(ptr)->setAge(args[0]->Uint32Value());
return Undefined();
}
而 getName
和 setName
的与上例类似。在对函数包装完成之后,需要将 Person 类暴露给脚本环境:
首先,创建一个新的函数模板,将其与字符串”Person”绑定,并放入 global:
Handle<FunctionTemplate> person_template = FunctionTemplate::New(PersonConstructor);
person_template->SetClassName(String::New("Person"));
global->Set(String::New("Person"), person_template);
然后定义原型模板:
Handle<ObjectTemplate> person_proto =person_template->PrototypeTemplate();
person_proto->Set("getAge", FunctionTemplate::New(PersonGetAge));
person_proto->Set("setAge", FunctionTemplate::New(PersonSetAge));
person_proto->Set("getName", FunctionTemplate::New(PersonGetName));
person_proto->Set("setName", FunctionTemplate::New(PersonSetName));
最后设置实例模板:
Handle<ObjectTemplate> person_inst =
person_template->InstanceTemplate();
person_inst->SetInternalFieldCount(1);
随后,创建一个用以测试的脚本:
//global function to print out detail info of person
function printPerson(person){
print(person.getAge()+":"+person.getName());
}
//new a person object
var person = new Person(26, "juntao");
//print it out
printPerson(person);
//set new value
person.setAge(28);
person.setName("juntao.qiu");
//print it out
printPerson(person);
运行得到以下结果:
26:juntao
28:juntao.qiu
{$ activeFileHint $}