轉載地址:http://blog.chinaunix.net/space.php?uid=8272118&do=blog&id=2033360
一、寫(xiě)在前面的話(huà)上回寫(xiě)到了關(guān)于如何在c++的代碼中嵌入v8引擎,實(shí)現javascript腳本中調用c++的函數以及從c++函數中調用javascript腳本中定義的函數(這其實(shí)就是設計模式中所謂的reflection,反射機制的具體實(shí)現)。通過(guò)了了幾行代碼就可以實(shí)現如此復雜的反射機制,由此可見(jiàn)v8引擎帶來(lái)了很大的便利性。但是上回偶只探索了一下實(shí)現c++與javascript腳本之間的函數相互調用,那么如何通過(guò)javascript調用c++的對象呢?按理說(shuō),javascript與c++的對象一定是應該可以互相調用的,但在實(shí)際的應用過(guò)程中,偶發(fā)現通過(guò)c++去調用javascript中定義的對象,似乎用得不那么多(主要是效率問(wèn)題),而從javascript調用c++中已經(jīng)定義好的對象,則是非常方便的,可以利用c++實(shí)現一些高效率的對象,然后由javascript進(jìn)行調用,從而兼顧了效率與靈活性?xún)蓚€(gè)方面。
對于c++代碼中如何為javascript導出一個(gè)對象供其調用,偶在網(wǎng)上google了一下這個(gè)問(wèn)題。比較有名的解釋就是另外一個(gè)叫做cproxyv8的開(kāi)源項目。該項目通過(guò)幾個(gè)代理類(lèi)可以很方便地將c++中的類(lèi)導出到j(luò )avascript空間中,例如,如果開(kāi)發(fā)者導出了一個(gè)叫做CPoint的類(lèi),那么在javascript腳本中可以這樣去使用:
var pt=new CPoint();
看上去貌似非常方便,但是偶查了一下cproxy的known issues,里面明確說(shuō)明了通過(guò)cproxyv8導出的c++類(lèi)是存在“內部釋放”(native disposed)的問(wèn)題的。
例如,如果偶這樣去編寫(xiě)javascipt腳本:
r = new Rect() ;
pt = r.topleft() ;
pt.x = 3 ;
pt.y = 4 ;
// 隔一大段代碼。。。
pt.x = 0 ; // 注意,這里或許就會(huì )出問(wèn)題
py.y = 10 ;
這一段腳本,看上去是沒(méi)有問(wèn)題的,r是Rect類(lèi)的對象(Rect類(lèi)是從c++中通過(guò)cproxyv8導出到j(luò )avascript中的一個(gè)類(lèi)),那么當調用r.topleft()的時(shí)候,就會(huì )返回一個(gè)Point對象,該對象是r對象的組成部分(Rect類(lèi)是由topleft和rightbottom兩個(gè)Point對象組合而成的),然后,偶們就可以使用pt對象對topleft的x,y坐標進(jìn)行操作。
但是現在問(wèn)題出現了,如果在后續javascript腳本中沒(méi)有任何代碼引用到r對象,那么對于v8引擎來(lái)說(shuō),這個(gè)r對象是可以釋放掉的,一旦r被釋放掉了,那么其內部的topleft和rightbottom兩個(gè)Point對象自然也會(huì )被釋放掉,如此以來(lái),pt變量引用的r.topleft()對象也被釋放掉了。此時(shí),如果再對pt進(jìn)行存取操作,就會(huì )引起錯誤和異常。這也就是所謂的“內部釋放”問(wèn)題了。當然,如果這里的Rect類(lèi)是在javascript腳本中定義的話(huà),則不會(huì )出現這種問(wèn)題。
偶個(gè)人以為,如果真的需要通過(guò)javascript操作和使用c++的對象,還是直接使用全局對象比較好。由于全局對象在javascript腳本執行的整個(gè)生命周期內都是可以訪(fǎng)問(wèn)的,因此,不會(huì )出現上述的內部釋放問(wèn)題。另外,對于cproxyv8,或許是偶鉆研得不夠深入,在最新版本的v8引擎上似乎不可以工作,還有就是一旦使用過(guò)程中出現了問(wèn)題,編譯器會(huì )輸出讓人頭昏眼花的錯誤提示信息,如果不仔細地一條條地追蹤,就會(huì )變得根本不知所云。因此,偶還是決定直接使用v8引擎中提供的類(lèi)和函數來(lái)導出全局的c++對象,而不是通過(guò)代理類(lèi)來(lái)解決問(wèn)題(當然,不可否認cproxyv8確實(shí)提供了極大的便利性!至于用不用這個(gè)cproxyv8,那完全是蘿卜白菜各有所好了)。
二、v8引擎導出c++類(lèi)的方法1、準備工作要想為javascript腳本導出一個(gè)全局的c++對象,那至少應該定義一個(gè)c++的類(lèi)吧?!
下面就是偶作實(shí)驗的時(shí)候聲明的一個(gè)c++類(lèi)(偶把它的名字姑且定為CFoo吧,以下是.h文件的定義):
#ifndef _FOO_H_
#define _FOO_H_
#include <v8.h>
#include "canvas.h"
class CFoo {
public:
CFoo(CCanvas * canv_ptr) ;
~CFoo() ;
private:
CCanvas * m_canv_ptr ; // 這里是保存外部的一個(gè)繪圖對象的指針,把真正的繪圖工作交給它做好了。
public: // member functions
inline void SetColor(int r, int g, int b) {
if(m_canv_ptr) {
m_canv_ptr->SetDrawColor(r, g, b) ;
}
}
inline void Line(int x1, int y1, int x2, int y2) {
if(m_canv_ptr) {
m_canv_ptr->DrawLine(x1, y1, x2, y2) ;
}
}
inline void Commit() {
if(m_canv_ptr) {
m_canv_ptr->Commit() ;
}
}
public: // 下面這些方法必須定義成static類(lèi)型的,用于給javascript腳本調用的。
static v8::Handle<v8::Value> set_color(const v8::Arguments & args) ;
static v8::Handle<v8::Value> line(const v8::Arguments & args) ;
static v8::Handle<v8::Value> commit(const v8::Arguments & args) ;
} ;
#endif
再往下是具體的實(shí)現,里面有一些奇怪的函數,稍后偶再來(lái)講解(以下是.cpp文件):
CFoo::CFoo(CCanvas * canv_ptr):m_canv_ptr(canv_ptr) {}
CFoo::~CFoo() { }
v8::Handle<v8::Value> CFoo::set_color(const v8::Arguments& args) {
CFoo * foo_ptr = util_unwrap_obj<CFoo>(args.Holder()) ; // 這里的util_unwrap_obj稍后再給出其定義
if(args.Length() == 3) {
foo_ptr->SetColor(args[0]->Int32Value(),
args[1]->Int32Value(),
args[2]->Int32Value()) ;
}
return v8::Undefined() ;
}
v8::Handle<v8::Value> CFoo::line(const v8::Arguments& args) {
CFoo * foo_ptr = util_unwrap_obj<CFoo>(args.Holder()) ;
if(args.Length() == 4) {
foo_ptr->Line(args[0]->Int32Value(),
args[1]->Int32Value(),
args[2]->Int32Value(),
args[3]->Int32Value()) ;
}
return v8::Undefined() ;
}
v8::Handle<v8::Value> CFoo::commit(const v8::Arguments& args) {
CFoo * foo_ptr = util_unwrap_obj<CFoo>(args.Holder()) ;
foo_ptr->Commit() ;
return v8::Undefined() ;
}
2、創(chuàng )建一個(gè)CFoo全局指針,然后在初始化的時(shí)候創(chuàng )建其對象static CFoo * g_foo_ptr ;
......
int main(int argc, char * argv[]) {
...
try {
g_foo_ptr = new CFoo(g_canv_ptr) ;
} catch(...) {
LOG("main, new CFoo object failed!") ;
delete g_canv_ptr ;
goto END_MAIN ;
}
...
return 0 ;
}
3、初始化javascript執行環(huán)境的時(shí)候,加入該全局對象(看上去似乎很復雜,但仔細看來(lái),實(shí)際上非常簡(jiǎn)單):
static void on_click(const char * js_fname, int x, int y) {
const int argc = 2 ;
HandleScope handle_scope ;
Handle<ObjectTemplate> global_templ ;
Handle<ObjectTemplate> foo_templ ;
Handle<External> foo_class_ptr ;
Handle<Object> foo_class_obj ;
Handle<Context> exec_context ;
Handle<String> js_source ;
Handle<Script> js_compiled ;
Handle<String> js_func_name ;
Handle<Value> js_func_val ;
Handle<Function> js_func ;
Handle<Value> argv[argc] ;
Handle<Integer> int_x ;
Handle<Integer> int_y ;
// 載入javascript腳本代碼,將代碼的文本內容保存在js_source對象中。
js_source = load_js(js_fname) ;
// 創(chuàng )建全局的對象模板(這個(gè)模板用于動(dòng)態(tài)創(chuàng )建對象的)
global_templ = = ObjectTemplate::New() ;
// 注冊全局函數,這些操作在偶的第一篇文章中已經(jīng)講解的非常清晰了。
global_templ->Set(String::New("set_draw_color"),
FunctionTemplate::New(set_draw_color)) ;
global_templ->Set(String::New("draw_line"),
FunctionTemplate::New(draw_line)) ;
global_templ->Set(String::New("commit"),
FunctionTemplate::New(commit)) ;
global_templ->Set(String::New("clear"),
FunctionTemplate::New(clear)) ;
global_templ->Set(String::New("draw_bmp"),
FunctionTemplate::New(draw_bmp)) ;
// 創(chuàng )建運行環(huán)境,創(chuàng )建的時(shí)候要傳入global_templ初始化預定義的全局函數環(huán)境
exec_context = Context::New(NULL, global_templ) ;
// 設置運行環(huán)境的有效范圍,如果context_scope被析構了,那也代表著(zhù)exec_context也被釋放掉了
Context::Scope context_scope(exec_context) ;
// 注意,從這一行開(kāi)始,就已經(jīng)準備往javascript運行環(huán)境中建立全局對象了
// 這里的ObjectTemplate是用于在運行時(shí)創(chuàng )建對象的(runtime object creator)
foo_templ = ObjectTemplate::New() ;
// 此句是設定使用該模板創(chuàng )建的對象有1個(gè)內部的數據區(internal field)
foo_templ->SetInternalFieldCount(1) ;
// 上面的代碼是用于創(chuàng )建一個(gè)對象模板的,下面這一行則是使用這個(gè)剛剛創(chuàng )建成功的對象模板創(chuàng )建出一個(gè)對象
foo_class_obj = foo_templ->NewInstance() ;
// 下面,我們就創(chuàng )建一個(gè)外部對象(External),這個(gè)對象其實(shí)就是在c++對象的外面包上一層皮
//(可以把Externl類(lèi)理解為一層包袱皮,把c++空間中的對象g_foo_ptr包裹在里面)
foo_class_ptr = External::New(static_cast<CFoo *>(g_foo_ptr)) ;
// 定義模板的時(shí)候已經(jīng)明確定義了由該模板創(chuàng )建出來(lái)的每一個(gè)對象內部一定有1個(gè)數據區(field)
// 現在我們就把包裹好的c++對象放到當前對象的內部數據區里面去
foo_class_obj->SetInternalField(0, foo_class_ptr) ;
// 為當前對象設置其對外函數接口
foo_class_obj->Set(String::New("set_draw_color"),
FunctionTemplate::New(CFoo::set_color)->GetFunction()) ;
foo_class_obj->Set(String::New("draw_line"),
FunctionTemplate::New(CFoo::line)->GetFunction()) ;
foo_class_obj->Set(String::New("commit"),
FunctionTemplate::New(CFoo::commit)->GetFunction()) ;
// 這一步是最關(guān)鍵的,從當前的javascript執行環(huán)境中獲取Global()全局環(huán)境對象,然后使用Set函數
// 把剛剛初始化完畢的foo_class_obj對象放到j(luò )avascript的全局執行環(huán)境中去
exec_context->Global()->Set(String::New("foo"),
foo_class_obj,
(PropertyAttribute)(v8::ReadOnly)) ;
// 這里就是常規的操作了,首先編譯javascript腳本
js_compiled = Script::Compile(js_source) ;
if(js_compiled.IsEmpty()) {
LOG("run_js, js_compiled is empty!") ;
return ;
}
// 運行腳本
js_compiled->Run() ;
// 然后下面的代碼是調用javascript腳本中定義的OnClick函數,同時(shí)將click的按鍵事件的x, y坐標傳入OnClick函數
js_func_name = String::New("OnClick") ;
js_func_val = exec_context->Global()->Get(js_func_name) ;
if(!js_func_val->IsFunction()) {
LOG("on_click, js_func_val->IsFunction check failed!") ;
} else {
js_func = Handle<Function>::Cast(js_func_val) ;
int_x = Integer::New(x) ;
int_y = Integer::New(y) ;
argv[0] = int_x ;
argv[1] = int_y ;
js_func->Call(exec_context->Global(), argc, argv) ;
}
}
ok,如此一來(lái),在javascript腳本中就可以不需要任何創(chuàng )建或者初始化,直接使用“foo”對象,以及該對象所導出的函數了。
在javascript腳本中可以這樣寫(xiě):
function OnClick(x, y) {
var rect_len = 50 ;
foo.set_draw_color(0x00, 0xff, 0x00) ;
foo.draw_line(x, y, x+rect_len, y) ;
foo.draw_line(x+rect_len, y, x+rect_len, y+rect_len) ;
foo.draw_line(x+rect_len, y+rect_len, x, y+rect_len) ;
foo.draw_line(x, y+rect_len, x, y) ;
foo.commit() ;
}
可能有朋友會(huì )問(wèn),這樣看上去似乎很麻煩,每次都要創(chuàng )建那么多東西,初始化那么多的對象,執行完畢以后,下次執行就又要重新初始化這些東西,效率是不是太低了一點(diǎn)?!其實(shí),偶是為了能夠讓代碼盡可能地清晰起見(jiàn)才這樣寫(xiě)的,把所有的初始化以及對象都以Handle<sometype>或Local<sometype>的方式定義的,一旦HandleScope被析構了,這些東西也就消失了。如果在真正的“有用的”代碼中,這里的很多初始化只需要一次就足夠了,大家可以使用Persistent<sometype>來(lái)聲明和初始化一些變量(例如:ObjectTemplate和Context),下次重復使用的時(shí)候直接拿來(lái)用就可以了,不需要反復地初始化。(但是需要注意一點(diǎn),用Persistent::New()聲明的變量,是需要調用Persistent::Dispose()進(jìn)行釋放的,這一點(diǎn)千萬(wàn)不要忘記了。)
4、最后,關(guān)于包袱皮的解開(kāi)問(wèn)題剛剛偶通過(guò)External給g_foo_ptr打了一個(gè)包袱皮,然后通過(guò)
foo_class_obj->SetInternalField(0, foo_class_ptr) ;
把包裹好的c++對象送到了javascript空間里面的對象中去了。那么,偶在javascript回調的時(shí)候該如何解開(kāi)這個(gè)包袱皮得到原始的c++對象呢?
現在該回過(guò)頭來(lái)看看剛剛關(guān)于foo.cpp的實(shí)現代碼中的一個(gè)遺留問(wèn)題了,以下面這個(gè)函數為例:
v8::Handle<v8::Value> CFoo::set_color(const v8::Arguments& args) {
CFoo * foo_ptr = util_unwrap_obj<CFoo>(args.Holder()) ;
if(args.Length() == 3) {
foo_ptr->SetColor(args[0]->Int32Value(),
args[1]->Int32Value(),
args[2]->Int32Value()) ;
}
return v8::Undefined() ;
}
這里有一個(gè)很有趣的函數:
util_unwrap_obj<CFoo>(args.Holder())
這個(gè)函數就是用來(lái)把包袱皮解開(kāi)的,其具體定義(參見(jiàn)utils.h文件)如下:
template<class T>
T* util_unwrap_obj(v8::Handle<v8::Object> obj) {
v8::Handle<v8::External> field = v8::Handle<v8::External>::Cast(obj->GetInternalField(0)) ;
void* raw_obj_ptr = field->Value() ;
return static_cast<T*>(raw_obj_ptr);
}
這個(gè)util_unwrap_obj是一個(gè)模板函數,就是用于解包袱皮的,這里args.Holder()得到的就是當前函數的所有者(也就是holder),從剛剛的代碼大家可以看到,這些函數的都是隸屬于foo對象的,因此,這里的args.Holder()函數返回的結果就是foo對象,該對象在定義對象模板的時(shí)候已經(jīng)定義了它將包含一個(gè)內部數據區(internalfield),而且在該對象初始化的時(shí)候,把g_foo_ptr給打上了個(gè)包袱皮,塞到了其內部數據區里面?,F在就可以反其道而行之:
v8::Handle<v8::External> field = v8::Handle<v8::External>::Cast(obj->GetInternalField(0)) ;
這一句,就是把foo對象內部數據區里面的數據給取出來(lái),大家可以看到其返回的值被強制轉換成External對象(也就是打好包袱皮的那個(gè)對象),然后通過(guò):
void* raw_obj_ptr = field->Value() ;
此句就是從包袱皮中把原始數據給取了出來(lái)(此時(shí),只是把g_foo_ptr的指針給取出來(lái)了,而且是void *類(lèi)型)。最后,通過(guò)強制類(lèi)型轉換,偶就得到了原始的g_foo_ptr指針:
return static_cast<T *>(raw_obj_ptr);
因此,偶在實(shí)現set_color這類(lèi)回調函數的時(shí)候就可以直接這樣用了:
v8::Handle<v8::Value> CFoo::set_color(const v8::Arguments& args) {
// 就是這一句,這個(gè)util_unwrap_obj模板函數解決了解包袱皮的任務(wù),而且,有了這個(gè)指向CFoo對象的指針以后
// 剩下的事情就一切簡(jiǎn)單了,通過(guò)指針調用CFoo對象的成員函數即可。
CFoo * foo_ptr = util_unwrap_obj<CFoo>(args.Holder()) ;
if(args.Length() == 3) {
foo_ptr->SetColor(args[0]->Int32Value(),
args[1]->Int32Value(),
args[2]->Int32Value()) ;
}
return v8::Undefined() ;
}
最后,為了方便共同研究和學(xué)習,偶把做實(shí)驗的代碼也給一并發(fā)上來(lái),文章中存在的任何問(wèn)題,歡迎各位高手不吝斧正,謝謝??!
PS:
測試腳本是t1.js,在使用的時(shí)候,可以:./test t1.js,然后用鼠標單擊屏幕即可看到效果。
| | 文件: | sdl_t5.tar.gz | | 大小: | 595KB | | 下載: | 下載 | |