当前位置: 首页 > news >正文

V8编程详解

这里选用了官方文档的一个非常简洁的HelloWorld.cc,代码如下:

#include <v8.h>
 
using namespace v8;
int main(int argc, char* argv[]) {
 
  // Create a stack-allocated handle scope.
  HandleScope handle_scope;
 
  // Create a new context.
  Persistent<Context> context = Context::New();
 
  // Enter the created context for compiling and
  // running the hello world script.
  Context::Scope context_scope(context);
 
  // Create a string containing the JavaScript source code.
  Handle<String> source = String::New("'Hello' + ', World!'");
 
  // Compile the source code.
  Handle<Script> script = Script::Compile(source);
 
  // Run the script to get the result.
  Handle<Value> result = script->Run();
 
  // Dispose the persistent context.
  context.Dispose();
 
  // Convert the result to an ASCII string and print it.
  String::AsciiValue ascii(result);
  printf("%s\n", *ascii);
  return 0;
}

我的目录结构如下:

编译运行:

g++ -I../include helloworld.cc  -o helloworld -lpthread -lv8
./helloworld

就可以在屏幕上看到输出结果了。

看到demo上有一些Context,Scope,Value等等,先不要慌张,其实就是V8的一些基本 数据类型,这些在后面会逐个一一讲到。                                                                                    

Handle<String> source = String::New("'Hello' + ', World!'");

看到这句话,其实就是在加载一个js文件了。只不过这个js文件内容为:

'Hello' + ', World!'

那么这里,source就已经是加载过的js文件字符串内容了,接下来V8需要对js进行编译解释。

Handle<Script> script = Script::Compile(source);
Handle<Value> result = script->Run();

最后就是JS的执行了。这里虽然只有简单的几个语句,但是V8对于JS的编译和运行做了很多很复杂的操作。下面将围绕这个Demo对V8的基本类型和相关概念进行讲解。

Handle

       在V8中,内存分配都是在V8的Heap中进行分配的,JavaScript的值和对象也都存放在V8的Heap中。这个Heap由V8独立的去维护,失去引用的对象将会被V8的GC掉并可以重新分配给其他对象。而Handle即是对Heap中对象的引用。V8为了对内存分配进行管理,GC需要对V8中的所有对象进行跟踪,而对象都是用Handle方式引用的,所以GC需要对Handle进行管理,这样GC就能知道Heap中一个对象的引用情况,当一个对象的Handle引用为发生改变的时候,GC即可对该对象进行回收(gc)或者移动。因此,V8编程中必须使用Handle去引用一个对象,而不是直接通过C++的方式去获取对象的引用,直接通过C++的方式去直接去引用一个对象,会使得该对象无法被V8管理。

       Handle分为Local和Persistent两种。从字面上就能知道,Local是局部的,它同时被HandleScope进行管理persistent,类似与全局的,不受HandleScope的管理,其作用域可以延伸到不同的函数,而Local是局部的,作用域比较小。Persistent Handle对象需要Persistent::New, Persistent::Dispose配对使用,类似于C++中new和delete。Persistent::MakeWeak可以用来弱化一个Persistent Handle,如果一个对象的唯一引用Handle是一个Persistent,则可以使用MakeWeak方法来如果该引用,该方法可以触发GC对被引用对象的回收。

HandleScope

一个函数中,可以有很多Handle,而HandleScope则相当于用来装Handle(Local)的容器,当HandleScope生命周期结束的时候,Handle也将会被释放,会引起Heap中对象引用的更新。HandleScope是分配在栈上,不能通过New的方式进行创建。对于同一个作用域内可以有多个HandleScope,新的HandleScope将会覆盖上一个HandleScope,并对Local Handle进行管理。下面通过代码来讲解HandleScope的生命周期:

#include <v8.h>
 
using namespace v8;
int main(int argc, char* argv[]) {
 
  // Create a stack-allocated handle scope.
  HandleScope handle_scope;
  // >>>>>>>>>>>>>>>>>>>>>>>>从这里开始,是HandleScope的生命周期的开始
  // 从此之后的所有Local Handle都这个handle_scope对象进行管理
 
 
  // Create a new context.
  Persistent<Context> context = Context::New();   //Persistent Handle
 
  // Enter the created context for compiling and
  // running the hello world script.
  Context::Scope context_scope(context);
 
  // Create a string containing the JavaScript source code.
  Handle<String> source = String::New("'Hello' + ', World!'"); //Local Handle
 
  // Compile the source code.
  Handle<Script> script = Script::Compile(source); //Local Handle
 
  // Run the script to get the result.
  Handle<Value> result = script->Run(); //Local Handle
 
  // Dispose the persistent context.
  context.Dispose();
 
  // Convert the result to an ASCII string and print it.
  String::AsciiValue ascii(result);
  printf("%s\n", *ascii);
  return 0;
  // <<<<<<<<<<<<<<<<<<<<<<<到这里,handle_scope的生命周期结束,其析构函数将被调用,其内部的所有Local Handle将被释放
}

Context

Context指的是JavaScript的执行环境。每个JavaScript都必须执行在一个Context中。Context有多个,而且可以在不同的Context中进行切换。

Persistent<Context> context = Context::New(); 
Context::Scope context_scope(context);

这段代码就是申请一个Persistent context,并通过Context::Scope切换到该context中。在这个Demo中,

Context::Scope context_scope(context);

之后的所有操作都执行在context中。

我们还可以使用

Persistent<Context> context_Ex = Context::New(); 
Context::Scope context_scope_Ex(context_Ex);

来切换到context_Ex中去。


从这张图可以比较清楚的看到Handle,HandleScope,以及被Handle引用的对象之间的关系。从图中可以看到,V8的对象都是存在V8的Heap中,而Handle则是对该对象的引用。

Context是V8中一个非常重要的类,理解起来就一句话:JavaScript的执行环境Context中包了JavaScript内建函数、对象等。所以,通过Context::New出来的Context都是一个全新的干净的JavaScript执行环境,切其他JavaScript环境的更改不影响New出来的Context的JavaScript执行环境,例如:修改JavaScript global函数。

注:使用了附录函数

#include "v8.h"
#include "utils.h"
 
#include <iostream>
 
using namespace v8;
using namespace std;
void contextAFunc() {
	HandleScope handle_scope;
	Persistent<Context> contextA = Context::New();
	Context::Scope context_scope(contextA);
	Handle<v8::String> js = ReadJS("script.js");
	Handle<Script> script = Script::Compile(js);
	Handle<Value> result = script->Run();
 
	script = Script::Compile(String::New("var result = Math.min(5, 9);"));
	result = script->Run();
	printValue(result);
	contextA.Dispose();
}
 
void contextBFunc() {
	HandleScope handle_scope;
	Persistent<Context> contextB = Context::New();
	Context::Scope context_scopeB(contextB);
 
	Handle<Script> script = Script::Compile(String::New("var result = Math.min(5, 9);"));
	Handle<Value> result = script->Run();
	printValue(result);
	contextB.Dispose();
}
 
int main(int argc, char** argv) {
	contextAFunc();
	contextBFunc();
	return 0;
}

script.js

Math.min = function(a, b) {return 1;}
var result = Math.min(5, 8);

Context之间的切换下图所示:


打印结果分别为:

1

5

可以看出,contextAFunc和contextBFunc分别位于不同的Context,在contextAFunc中对全局函数做了更改,所以第一次打印无论如何都是1,而contextBFunc中,New了一个contextB,整个js执行在contextB中,拥有一个全新的干净的JavaScript执行环境,所以,打印是5。

V8编程中,需要在不同的Context中进行切换,需要创建各种Context。这样会不会影响执行效率呢?V8已经考虑到这一点了,V8在除了创建第一个Context的时候,消耗的时间较长外,创建其他的Context的时候,开销非常小,V8对Context需要创建的一些JavaScript的内建对象即可。

Context常用函数讲解:

static Persistent<Context> New(
      ExtensionConfiguration* extensions = NULL,
      Handle<ObjectTemplate> global_template = Handle<ObjectTemplate>(),
      Handle<Value> global_object = Handle<Value>());

extensions 给新创建的Context创建扩展,一般都是NULL

global_template,这个参数比较有用,Context中的JavaScript的global对象是通过这个global_template创建的。当我们需要给JavaScript的global添加C++函数是,一般都是通过这个给这个global_template添加属性从而传递给JavaScript的global对象的。

golobal_object,其目的是是的global对象得到复用。可以是的不同的Context有相同的global对象。前提条件是必须使用相同的global_template。

void Enter();
void Exit();

用于不同的Context之间切换。

本章主要来讲讲如何通过V8来实现JS调用C++。JS调用C++,分为JS调用C++函数(全局),和调用C++类。

JS调用C++函数

JS调用C++函数,就是通过FunctionTemplate和ObjectTemplate进行扩展的。

FunctionTemplate,ObjectTemplate可以理解为JS function和C++ 函数之间的binding。FunctionTemplate实现了JS函数和C++函数的绑定,当然这种绑定是单向的,只能实现JS调用C++的函数。说的更直白一点,FunctionTemplate和ObjectTemplate就相当于JS的function和object。

基本原理就是先将C++ 函数通过FunctionTemplate实现绑定,然后将这个FunctionTemplate注册到JS的global上去,这样,JS就可以调用C++函数了。

代码如下:

上面这段代码实现了在JS调用C++ Yell()函数。

基本步骤分为A, B , C三步:

#include "v8.h"
#include <string.h>
#include <stdio.h>
 
using namespace v8;
using namespace std;
 
 
Handle<Value> Yell(const Arguments& args) {
	HandleScope  handle_scope;
	char buffer[4096];
	
	memset(buffer, 0, sizeof(buffer));
	Handle<String> str = args[0]->ToString();
	str->WriteAscii(buffer);
	printf("Yell: %s\n", buffer);
 
	return Undefined();
}
 
int main(int argc, char** argv) {
	HandleScope handle_scope;
	//A
	Handle<FunctionTemplate> fun = FunctionTemplate::New(Yell);
	//B
	Handle<ObjectTemplate> global = ObjectTemplate::New();
	global->Set(String::New("yell"), fun);
	//C
	Persistent<Context> cxt = Context::New(NULL, global);
 
	Context::Scope context_scope(cxt);
	Handle<String> source = String::New("yell('Google V8!')");
	Handle<Script> script = Script::Compile(source);
	Handle<Value> result = script->Run();
 
	cxt.Dispose();
}

第一步,定义一个FunctionTempte并与C++函数绑定:

Handle<FunctionTemplate> fun = FunctionTemplate::New(Yell);

第二步,定义一个ObectTemplate,并向该对象注册一个FunctionTemplate

Handle<ObjectTemplate> global = ObjectTemplate::New();
global->Set(String::New("yell"), fun);

第三步,将该对象注册到JS的global中去:

Persistent<Context> cxt = Context::New(NULL, global);

JS调用C++类

JS其实是无法直接使用C++类的,当JS中new一个对象的时候,需要手动将C++产生的对象同JS的对象进行绑定。从而就造成了JS使用C++类的假象:

var cloudapp = new CloudApp();
cloudapp.xxInterface();

这一点V8做的不够强大,而Qt的QML(类JS脚本语言)就能实现自动绑定。

InternalField

当JS new一个对象的时候,C++中也会同步的new一个对象并将该指针保存在C++内部,并维护这个指针list,这就是V8 InternalField的作用。所有需要跟JS绑定的C++指针都存在这个InternalField中,其实就是一个list,一个V8 Object可以拥有任意数量的InternalField。如果需要使用保存在InterField中的C++指针,直接Get出来即可:

将C++指针封装到InternalField中:

//....
void* ptr = ...
object->SetInternalField(0, External::New(ptr));

上面这段代码将一个C++指针ptr保存在InternalField的index 0处。然后将来的某个时候如果需要获取这个指针,只需使用index 0来获取该指针。

将C++指针从InternalField中获取出来:

Local<External> wrap = Local<External>::Cast(object->GetInternalField(0));
void* ptr = wrap->Value();

object->GetInternalField(0)就是从InternalField取出index=0处的C++指针。

External

既然说到C++指针的绑定,就必须说一下V8的External了。V8的External就是专门用来封装(Wrap)和解封(UnWrap)C++指针的。V8的External 实现如下:

Local<Value> External::Wrap(void* value) {
  return External::New(value);
}
 
 
void* External::Unwrap(Handle<v8::Value> obj) {
  return External::Cast(*obj)->Value();
}

 

External其实就是C++指针的载体。这也就解释了前面在InternalField中设置和获取InternalField中的C++指针的时候,使用了External::New和wrap->Value()的原因了。External::Value()返回的就是C++指针。

下面开始上代码,看看究竟是如何实现JS调用C++类的:

//C++Externtion
#include "v8.h"
#include "utils.h"
 
#include <iostream>
#include <string>
 
using namespace std;
 
using namespace v8;
 
enum AppState{
	IDEL = 0,
	LOADED,
	STOP
};
 
class CloudApp {
public:
	CloudApp(int id) { 
		state = IDEL;
		appId = id;
	}
	void start() {
		cout << "CloudApp been Loaded id = " << appId << endl;
		state = LOADED;
	};
 
	int getState() { return state;}
	int getAppId() { return appId;}
	
private:
	AppState state;
	int appId;	
};
 
//向MakeWeak注册的callback.
void CloudAppWeakReferenceCallback(Persistent<Value> object, void * param) {
	if (CloudApp* cloudapp = static_cast<CloudApp*>(param)) {
		delete cloudapp;
	}
}
 
//将C++指针通过External保存为Persistent对象,避免的指针被析构
Handle<External> MakeWeakCloudApp(void* parameter) {
	Persistent<External> persistentCloudApp = Persistent<External>::New(External::New(parameter));
		
//MakeWeak非常重要,当JS世界new一个CloudApp对象之后
//C++也必须new一个对应的指针。
//JS对象析构之后必须想办法去析构C++的指针,可以通过MakeWeak来实现,
//MakeWeak的主要目的是为了检测Persistent Handle除了当前Persistent 
//的唯一引用外,没有其他的引用,就可以析构这个Persistent Handle了,
//同时调用MakeWeak的callback。这是我们可以再这个callback中delete 
//C++指针
	persistentCloudApp.MakeWeak(parameter, CloudAppWeakReferenceCallback);
 
	return persistentCloudApp;
}
 
//将JS传进来的参数解析之后,创建C++对象
CloudApp* NewCloudApp(const Arguments& args) {
	CloudApp* cloudApp = NULL;
	
	if (args.Length() == 1) {
		cloudApp = new CloudApp(args[0]->ToInt32()->Value());	
	} else {
		v8::ThrowException(String::New("Too many parameters for NewCloudApp"));
	}
 
	return cloudApp;
}
 
//相当于JS对应的构造函数,当JS中使用new CloudApp的时候,这个callback将自动被调用
Handle<Value> CloudAppConstructCallback(const Arguments& args) {
	if (!args.IsConstructCall())
		return Undefined();
	
	CloudApp* cloudapp = NewCloudApp(args);
	Handle<Object> object = args.This();
 
	object->SetInternalField(0, MakeWeakCloudApp(cloudapp));
 
	return Undefined();
}
 
Handle<Value> GetState(const Arguments& args) {
	Handle<Object> self = args.Holder();
 
	Local<External> wrap = Local<External>::Cast(self->GetInternalField(0));
	void* ptr = wrap->Value();
	CloudApp* cloudapp = static_cast<CloudApp*>(ptr);
 
	return Integer::New(cloudapp->getState());
}
 
Handle<Value> GetAppId(const Arguments& args) {
	Handle<Object> self = args.Holder();
 
	Local<External> wrap = Local<External>::Cast(self->GetInternalField(0));
	void* ptr = wrap->Value();
	CloudApp* cloudapp = static_cast<CloudApp*>(ptr);
 
	return Integer::New(cloudapp->getAppId());
} 
 
Handle<Value> Start(const Arguments& args) {
	Handle<Object> self = args.Holder();
 
	Local<External> wrap = Local<External>::Cast(self->GetInternalField(0));
	void* ptr = wrap->Value();
	CloudApp* cloudapp = static_cast<CloudApp*>(ptr);
 
	cloudapp->start();
 
	return Undefined();
}
 
void SetupCloudAppInterface(Handle<ObjectTemplate> global) {
	Handle<FunctionTemplate> cloudapp_template = FunctionTemplate::New(CloudAppConstructCallback);
	cloudapp_template->SetClassName(String::New("CloudApp"));
 
	Handle<ObjectTemplate> cloudapp_proto = cloudapp_template->PrototypeTemplate();
	//这一步,完全可以使用cloudapp_inst->Set(....)
	//使用prototype更符合JS编程
	cloudapp_proto->Set(String::New("start"), FunctionTemplate::New(Start));
	cloudapp_proto->Set(String::New("state"), FunctionTemplate::New(GetState));
	cloudapp_proto->Set(String::New("appid"), FunctionTemplate::New(GetAppId));
	
	//******很重要!!!
	Handle<ObjectTemplate> cloudapp_inst = cloudapp_template->InstanceTemplate();
	cloudapp_inst->SetInternalFieldCount(1);
	
	//向JS世界注册一个函数,其本质就是向JS世界的global注册一个类。
	//所以,也是通过向global注入CloudApp类。
	global->Set(String::New("CloudApp"), cloudapp_template);
}
 
void InitialnilizeInterface(Handle<ObjectTemplate> global) {
	SetupCloudAppInterface(global);
}
 
void LoadJsAndRun() {
	Handle<String> source = ReadJS("script.js");
	Handle<Script> script = Script::Compile(source);
	Handle<Value> result = script->Run();
 
	printValue(result);
}
 
void Regist2JsContext(Handle<ObjectTemplate>& object, Persistent<Context>& context) {
	context = Context::New(NULL, object);
}
 
int main(int argc, char** argv) {
	HandleScope handle_scope;
	Handle<ObjectTemplate> global = ObjectTemplate::New();
	Persistent<Context> context;
	
	InitialnilizeInterface(global);
	Regist2JsContext(global, context);
	Context::Scope context_scope(context);
	LoadJsAndRun();
 
	context.Dispose();
	
	return 0;
}

JS代码如下:

//script.js
var cloudapp = new CloudApp(24);
cloudapp.start();
var result;

上面的代码基本可以从函数名称和注释中明白是什么意思。最后再讲一点SetInternalFieldCount:

Handle<ObjectTemplate> cloudapp_inst = cloudapp_template->InstanceTemplate();
cloudapp_inst->SetInternalFieldCount(1);

在其他的操作都就绪之后还必须SetInsternalFieldCount(),这一点是为了告诉V8,我们有几个InternalField,这里是只有1个。否则,在JS和C++指针交互过程中,V8在查找InternalField的时候会越界的。

Google V8编程详工具函数

头文件:utils.h

#ifndef UTILS_H_
#define UTILS_H_
#include "v8.h"
#include <iostream>
using namespace v8;
using namespace std;
 
v8::Handle<v8::String> ReadJS(const char* name);
void printValue(Handle<Value> result);
#endif
  • ReadJS
v8::Handle<v8::String> ReadJS(const char* name) {
 
        FILE* file = fopen(name, "rb");
 
        if (file == NULL) {
                return v8::Handle<v8::String>();
        }
 
        fseek(file, 0, SEEK_END);
        int size = ftell(file);
        rewind(file);
 
        char* chars = new char[size + 1];
        chars[size] = '\0';
 
        for (int i = 0; i < size;) {
                int read = fread(&chars[i], 1, size - i, file);
                i += read;
        }
 
        fclose(file);
 
        v8::Handle<v8::String> result = v8::String::New(chars, size);
        delete[] chars;
        return result;
}
  • printValue
void printValue(Handle<Value> result) {
	//感谢downmooner兄的提醒,这个String::Utf8Value str(result)的确让这段代码
	//南辕北辙
	//String::Utf8Value str(result);
 
 
	// just construct the "result" script
	Handle<Script> script = Script::Compile(String::New("result"));
	result = script->Run();
	cout << *String::Utf8Value(result) << endl;
}

 

相关文章:

  • 代理服务器
  • sqlite加密
  • x86/x64/x86_64/i386/ia32/ia64/amd/amd64 辨析
  • 理清gcc、libc、libstdc++的关系
  • gcc/g++/clang/cl编译器
  • 深入浅出让你理解什么是LLVM
  • Ninja - chromium核心构建工具
  • depot_tools
  • 智能指针 unique_ptr 详解
  • C++11中“= delete;“的使用
  • C++Error2208:...尝试引用已删除的函数
  • Ninja 构建系统
  • ICU
  • 交叉编译详解
  • GYP,GN和Ninja
  • Bootstrap JS插件Alert源码分析
  • ComponentOne 2017 V2版本正式发布
  • Java 内存分配及垃圾回收机制初探
  • JavaScript HTML DOM
  • linux学习笔记
  • nodejs实现webservice问题总结
  • npx命令介绍
  • Python连接Oracle
  • React-redux的原理以及使用
  • Redash本地开发环境搭建
  • SegmentFault 技术周刊 Vol.27 - Git 学习宝典:程序员走江湖必备
  • uni-app项目数字滚动
  • Vue2.0 实现互斥
  • 第2章 网络文档
  • 开放才能进步!Angular和Wijmo一起走过的日子
  • 如何选择开源的机器学习框架?
  • 如何用Ubuntu和Xen来设置Kubernetes?
  • 使用docker-compose进行多节点部署
  • PostgreSQL 快速给指定表每个字段创建索引 - 1
  • ​DB-Engines 11月数据库排名:PostgreSQL坐稳同期涨幅榜冠军宝座
  • ​二进制运算符:(与运算)、|(或运算)、~(取反运算)、^(异或运算)、位移运算符​
  • ​一些不规范的GTID使用场景
  • #14vue3生成表单并跳转到外部地址的方式
  • (安卓)跳转应用市场APP详情页的方式
  • (附源码)计算机毕业设计ssm基于B_S的汽车售后服务管理系统
  • (力扣记录)1448. 统计二叉树中好节点的数目
  • (四)Linux Shell编程——输入输出重定向
  • (原創) 物件導向與老子思想 (OO)
  • (中等) HDU 4370 0 or 1,建模+Dijkstra。
  • .java 9 找不到符号_java找不到符号
  • .NET企业级应用架构设计系列之应用服务器
  • @private @protected @public
  • @staticmethod和@classmethod的作用与区别
  • @value 静态变量_Python彻底搞懂:变量、对象、赋值、引用、拷贝
  • [ HTML + CSS + Javascript ] 复盘尝试制作 2048 小游戏时遇到的问题
  • [100天算法】-实现 strStr()(day 52)
  • [2016.7 test.5] T1
  • [ABC294Ex] K-Coloring
  • [AI]ChatGPT4 与 ChatGPT3.5 区别有多大
  • [ajaxupload] - 上传文件同时附件参数值