Google V8编程详解(五)JS调用C++

简介:

最近由于忙着解决个人单身的问题,时隔这么久才更新第五章。

上一章主要讲了Google V8的Context概念。那么其实Google V8的基本概念还有FunctionTemplate, ObjectTemplate等比较重要的基本概念,这些概念将在后续章节中进行渗透。

本章主要来讲讲如何通过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的时候会越界的。

版权申明:
转载文章请注明原文出处,任何用于商业目的,请联系本人:hyman_tan@126.com

目录
相关文章
|
30天前
|
C++
C++ 语言异常处理实战:在编程潮流中坚守稳定,开启代码可靠之旅
【8月更文挑战第22天】C++的异常处理机制是确保程序稳定的关键特性。它允许程序在遇到错误时优雅地响应而非直接崩溃。通过`throw`抛出异常,并用`catch`捕获处理,可使程序控制流跳转至错误处理代码。例如,在进行除法运算或文件读取时,若发生除数为零或文件无法打开等错误,则可通过抛出异常并在调用处捕获来妥善处理这些情况。恰当使用异常处理能显著提升程序的健壮性和维护性。
42 2
|
24天前
|
JavaScript 前端开发 编译器
解锁JavaScript模块化编程新纪元:从CommonJS的基石到ES Modules的飞跃,探索代码组织的艺术与科学
【8月更文挑战第27天】随着Web应用复杂度的提升,JavaScript模块化编程变得至关重要,它能有效降低代码耦合度并提高项目可维护性及扩展性。从CommonJS到ES Modules,模块化标准经历了显著的发展。CommonJS最初专为服务器端设计,通过`require()`同步加载模块。而ES Modules作为官方标准,支持异步加载,更适合浏览器环境,并且能够进行静态分析以优化性能。这两种标准各有特色,但ES Modules凭借其更广泛的跨平台兼容性和现代语法逐渐成为主流。这一演进不仅标志着JavaScript模块化的成熟,也反映了整个JavaScript生态系统的不断完善。
34 3
|
30天前
|
算法 C语言 C++
C++语言学习指南:从新手到高手,一文带你领略系统编程的巅峰技艺!
【8月更文挑战第22天】C++由Bjarne Stroustrup于1985年创立,凭借卓越性能与灵活性,在系统编程、游戏开发等领域占据重要地位。它继承了C语言的高效性,并引入面向对象编程,使代码更模块化易管理。C++支持基本语法如变量声明与控制结构;通过`iostream`库实现输入输出;利用类与对象实现面向对象编程;提供模板增强代码复用性;具备异常处理机制确保程序健壮性;C++11引入现代化特性简化编程;标准模板库(STL)支持高效编程;多线程支持利用多核优势。虽然学习曲线陡峭,但掌握后可开启高性能编程大门。随着新标准如C++20的发展,C++持续演进,提供更多开发可能性。
45 0
|
21天前
|
Rust 安全 C++
系统编程的未来之战:Rust能否撼动C++的王座?
【8月更文挑战第31天】Rust与C++:现代系统编程的新选择。C++长期主导系统编程,但内存安全问题频发。Rust以安全性为核心,通过所有权和生命周期概念避免内存泄漏和野指针等问题。Rust在编译时确保内存安全,简化并发编程,其生态系统虽不及C++成熟,但发展迅速,为现代系统编程提供了新选择。未来有望看到更多Rust驱动的系统级应用。
38 1
|
7天前
|
程序员 C++ 容器
C++编程基础:命名空间、输入输出与默认参数
命名空间、输入输出和函数默认参数是C++编程中的基础概念。合理地使用这些特性能够使代码更加清晰、模块化和易于管理。理解并掌握这些基础知识,对于每一个C++程序员来说都是非常重要的。通过上述介绍和示例,希望能够帮助你更好地理解和运用这些C++的基础特性。
21 0
|
30天前
|
JavaScript 前端开发 安全
揭秘TypeScript的魔力:它是如何华丽变身为JavaScript的超能英雄,让您的代码飞入全新的编程维度!
【8月更文挑战第22天】在Web开发领域,JavaScript是最主流的编程语言之一。但随着应用规模的增长,其类型安全和模块化的不足逐渐显现。为解决这些问题,微软推出了TypeScript,这是JavaScript的一个超集,通过添加静态类型检查来提升开发效率。TypeScript兼容所有JavaScript代码,并引入类型注解功能。
28 2
|
7天前
|
JavaScript 前端开发 测试技术
一个google Test文件C++语言案例
这篇文章我们来介绍一下真正的C++语言如何用GTest来实现单元测试。
9 0
|
20天前
|
JavaScript 前端开发 Oracle
|
2月前
|
人工智能 JavaScript 开发工具
C++中的AI编程助手添加
今天为大家推荐一款适配了 Viusal Studio(本文使用),VS Code(本文使用),JetBrains系列以及Vim等多种编译器环境的插件 Fitten Code,Fitten Code 是由非十大模型驱动的 AI 编程助手,它可以自动生成代码,提升开发效率,帮您调试 Bug,节省您的时间,另外还可以对话聊天,解决您编程碰到的问题。 Fitten Code免费且支持 80 多种语言:Python、C++、Javascript、Typescript、Java等。
76 8
|
28天前
|
JavaScript 前端开发 开发者
震撼揭秘!JS模块化进化史:从混沌到秩序,一场代码世界的华丽蜕变,你怎能错过这场编程盛宴?
【8月更文挑战第23天】在 Web 前端开发领域,JavaScript 模块化已成为处理日益复杂的 Web 应用程序的关键技术。通过将代码分解成独立且可重用的模块,开发者能够更有效地组织和管理代码,避免命名冲突和依赖混乱。从最早的全局函数模式到 IIFE,再到 CommonJS 和 AMD,最终进化到了 ES6 的原生模块支持以及 UMD 的跨环境兼容性。本文通过具体示例介绍了这些模块化规范的发展历程及其在实际开发中的应用。
29 0