閱讀30 返回首頁    go 汽車大全


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

最後更新:2017-04-04 07:03:20

  上一篇:go 老板要放下架子去請人才
  下一篇:go 中國芯片將成為英特爾同級競爭對手