JavaScript是一種普遍用於Web客戶端開發的腳本語言,經常使用來控制瀏覽器的DOM樹,給HTML網頁添加動態功能。目前JavaScript遵循的web標準的是ECMAScript262。因爲JavaScript提供了豐富的內置函數、良好的對象機制。因此JavaScript還能夠嵌入到某一種宿主語言中,彌補宿主語言的表現力,從而實現快速、靈活、可定製的開發。 軟件程序應用javascript 現有的主流瀏覽器基本上都實現了一個本身的JavaScript引擎。這些JavaScript引擎能夠分析、編譯和執行JavaScript腳本。這些JavaScript引擎都是用C或者C++語言寫的,都對外提供了API接口。因此在C、C++語言中使用這些JavaScript引擎,嵌入JavaScript是很是方便的。有一些著名的開源項目都使用了這一種方式,來進行混合的編程,好比Node.js, K-3D等。javascript
已知著名的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進行雙向交互了。 V8對外的API接口是C++的接口。V8的API定義了幾個基本概念:句柄(handle),做用域(scope),上下文環境(Context)。模板(Templates),瞭解這些基本的概念纔可使用V8。java
l 上下文環境Context就是腳本的運行環境,JavaScript的變量、函數等都存在於上下文環境Context中。Context能夠嵌套,即當前函數有一個Context,調用其它函數時若是又有一個Context,則在被調用的函數中javascript是以最近的Context爲準的,當退出這個函數時,又恢復到了原來的Context。web
l 句柄(handle)就是一個指向V8對象的指針,有點像C++的智能指針。全部的v8對象必須使用句柄來操做。沒有句柄指向的V8對象,很快會被垃圾回收器回收了。編程
l 做用域(scope)是句柄的容器,一個做用域(scope)能夠有不少句柄(handle)。當離開一個做用域(scope)時,全部在做用域(scope)裏的句柄(handle)都會被釋放了。瀏覽器
l 模板(Templates)分爲函數模板和對象模板,是V8對JavaScript的函數和對象的封裝。方便C++語言操做JavaScript的函數和對象。ide
l V8 API定義了一組類或者模板,用來與JavaScript的語言概念一一對應。好比:函數
V8的 Function模板與JavaScript的函數對應設計
V8的Object類與JavaScript的對象對應指針
V8的String類與JavaScript的字符對應code
V8的Script類與JavaScript的腳本文本對應,它能夠編譯並執行一段腳本。
2.2. C++調用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);
2.3. JavaScript調用C++
使用V8,在Javascript腳本中想要訪問C++中的內容,必須先將C++的變量、函數、類注入到Javacsript中。注入時,首先要調用Context::GetCurrent()->Global()獲取到Global對象,再經過Global對象的Set函數來注入全局變量、全局函數、類對象。
全局變量、全局函數的注入過程與上一節的代碼相似,這裏就省略不寫了。比較麻煩的是將C++的類、類對象注入到Javascript中。其基本的過程以下:
首先定義一個C++類,定義一個類對象指針
定義一組C++全局函數,封裝V8對C++類的調用,提供給V8進行CALLBACK回調。
最後調用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::Handlev8::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::Handlev8::Value testFunc(constv8::Arguments& args)
{
//獲取構造函數testConstructor時,設置的對象指針 v8::Local<v8::Object>self = args.Holder();
v8::Localv8::External wrap =v8::Localv8::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::Handlev8::ValuegetTestNumber(v8::Localv8::String property, const v8::AccessorInfo& info)
{
//獲取構造函數testConstructor時,設置的對象指針 v8::Local<v8::Object>self = info.Holder();
v8::Localv8::External wrap =v8::Localv8::External::Cast(self->GetInternalField(0));
void* ptr =wrap->Value(); //返回類變量 returnv8::Int32::New(static_cast<test*>(ptr)->number);
}
//C++類和全局的函數定義好之後,就能夠開始將類、變量注入V8 Javascript中了
//獲取global對象
v8::Handlev8::Object globalObj =context->Global();
//新建一個函數模板,testConstructor是上面定義的全局函數
v8::Handlev8::FunctionTemplate test_templ =v8::FunctionTemplate::New(testConstructor);
//設置類名稱
test_templ->SetClassName(v8::String::New("test"));
//獲取Prototype,
v8::Handlev8::ObjectTemplate test_proto =test_templ->PrototypeTemplate();
//增長類成員函數,testFunc是上面定義的全局函數
test_proto->Set("func",v8::FunctionTemplate::New(testFunc));
//設置兩個內部數據,用於構造函數testConstructor時,存放類對象的指針。
v8::Handlev8::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::Handlev8::Function point_ctor =test_templ->GetFunction();
globalObj->Set(v8::String::New("test"),point_ctor);
//新建一個test對象,並使得g_test綁定新建的對象
v8::Handlev8::Value flag = v8::Int32::New(1);
v8::Handlev8::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; end.