C++使用V8
原文地址:http://www.codeproject.com/KB/library/Using_V8_Javascript_VM.aspx
介紹
誰不想知道虛擬機是怎樣工作的?不過,比起自己寫一個虛擬機,更好的辦法是使用大公司的產品。在這篇文章中,我將介紹如何在你的程序中使用V8——谷歌瀏覽器(Chrome)所使用的開源JavaScript引擎。
背景
這里的代碼使用V8作為嵌入庫來執行JavaScript代碼。要取得庫源碼和其它信息,可以瀏覽V8開發者頁面。想有效地應用V8,你需要了解C/C++和JavaScript。
使用
我們來看看演示中有哪些東西:
·???????????????????? 如何使用V8庫API來執行JavaScript腳本。
·???????????????????? 如何存取腳本中的整數和字符串。
·???????????????????? 如何建立可被腳本調用的自定義函數。
·???????????????????? 如何建立可被腳本調用的自定義類。
首先,我們一起了解一下怎樣初始化V8。這是嵌入V8引擎的簡單例子:
1.??????????????? #include <v8.h>
2.??????????????? using namespace v8;
3.??????????????? int main(int argc, char* argv[]) {
4.??????????????? ? // Create a stack-allocated handle scope.
5.??????????????? ? HandleScope handle_scope;
6.??????????????? ? // Create a new context.
7.??????????????? ? Handle<Context> context = Context::New();
8.??????????????? ? // Enter the created context for compiling and
9.??????????????? ? // running the hello world script.
10.??????????? ? Context::Scope context_scope(context);
11.??????????? ? // Create a string containing the JavaScript source code.
12.??????????? ? Handle<String> source = String::New("'Hello' + ', World!'");
13.??????????? ? // Compile the source code.
14.??????????? ? Handle<Script> script = Script::Compile(source);
15.??????????? ? // Run the script to get the result.
16.??????????? ? Handle<Value> result = script->Run();
17.??????????? ? // Convert the result to an ASCII string and print it.
18.??????????? ? String::AsciiValue ascii(result);
19.??????????? ? printf("%s ", *ascii);
20.??????????? ? return 0;
21.??????????? }
好了,不過這還不能說明怎樣讓我們控制腳本中的變量和函數。
全局模型(The Global Template)
首先,我們需要一個全局模型來掌控我們所做的修改:
1.??????????????? v8::Handle<v8::ObjectTemplate> global = v8::ObjectTemplate::New();
這里建立了一個新的全局模型來管理我們的上下文(context)和定制。在V8里,每個上下文是分開的,它們有自己的全局模型。一個上下文就是一個獨立的執行環境,相互之間沒有關聯,JavaScript運行于其中一個實例之中。
自定義函數
接下來,我們加入一個名為"plus"的自定義函數:
1.??????????????? // plus function implementation - Add two numbers
2.??????????????? v8::Handle<v8::Value> Plus(const v8::Arguments& args)
3.??????????????? {
4.??????????????? ??? unsigned int A = args[0]->Uint32Value();
5.??????????????? ??? unsigned int B = args[1]->Uint32Value();
6.??????????????? ??? return v8_uint32(A +? B);
7.??????????????? }
8.??????????????? //...
9.??????????????? //associates plus on script to the Plus function
10.??????????? global->Set(v8::String::New("plus"), v8::FunctionTemplate::New(Plus));
自定義函數必須以const v8::Arguments&作為參數并返回v8::Handle<v8::Value>。我們把這個函數加入到模型中,關聯名稱"plus"到回調Plus。現在,在腳本中每次調用"plus",我們的Plus函數就會被調用。這個函數只是返回兩個參數的和。
現在我們可以在JavaScript里使用這個自定義函數了:
plus(120,44);?
在腳本里也可以得到函數的返回值:
x = plus(1,2);
if( x == 3){
?? // do something important here!
}
訪問器(Accessor)——存取腳本中的變量
現在,我們可以建立函數了...不過如果我們可以在腳本外定義一些東西豈不是更酷?Let's do it! V8里有個東東稱為存取器(Accessor),使用它,我們可以關聯一個名稱到一對Get/Set函數上,V8會用它來存取腳本中的變量。
1.??????????????? global->SetAccessor(v8::String::New("x"), XGetter, XSetter);
這行代碼關聯名稱"x"到XGetter和XSetter函數。這樣在腳本中每次讀取到"x"變量時都會調用XGetter,每次更新"x"變量時會調用XSetter。下面是這兩個函數的代碼:
1.??????????????? //the x variable!
2.??????????????? int x;
3.??????????????? //get the value of x variable inside javascript
4.??????????????? static v8::Handle<v8::Value> XGetter( v8::Local<v8::String> name,
5.??????????????? ????????????????? const v8::AccessorInfo& info) {
6.??????????????? ? return? v8::Number::New(x);
7.??????????????? }
8.??????????????? //set the value of x variable inside javascript
9.??????????????? static void XSetter( v8::Local<v8::String> name,
10.??????????? ?????? v8::Local<v8::Value> value, const v8::AccessorInfo& info) {
11.??????????? ? x = value->Int32Value();
12.??????????? }
XGetter里我們把"x"轉換成V8喜歡的數值類型。XSetter里,我們把傳入的參數轉換成整數,Int32Value是基本類型轉換函數的一員,還有NumberValue對應double、BooleanValue對應bool,等。
現在,我們可以為字符串做相同的操作:
1.??????????????? //the username accessible on c++ and inside the script
2.??????????????? char username[1024];
3.??????????????? //get the value of username variable inside javascript
4.??????????????? v8::Handle<v8::Value> userGetter(v8::Local<v8::String> name,
5.??????????????? ?????????? const v8::AccessorInfo& info) {
6.??????????????? ??? return v8::String::New((char*)&username,strlen((char*)&username));
7.??????????????? }
8.??????????????? //set the value of username variable inside javascript
9.??????????????? void userSetter(v8::Local<v8::String> name, v8::Local<v8::Value> value,
10.??????????? ??? const v8::AccessorInfo& info) {
11.??????????? ??? v8::Local<v8::String> s = value->ToString();
12.??????????? ??? s->WriteAscii((char*)&username);
13.??????????? }
對于字符串,有一點點不同,"userGetter"和XGetter做的一樣,不過userSetter要先用ToString方法取得內部字符串,然后用WriteAscii函數把內容寫到我們指定的內存中。現在,加入存取器:
1.??????????????? //create accessor for string username
2.??????????????? global->SetAccessor(v8::String::New("user"),userGetter,userSetter);
打印輸出
"print"函數是另一個自定義函數,它通過"printf"輸出所有的參數內容。和之前的"plus"函數一樣,我們要在全局模型中注冊這個函數:
1.??????????????? //associates print on script to the Print function
2.??????????????? global->Set(v8::String::New("print"), v8::FunctionTemplate::New(Print));
實現"print"函數
1.??????????????? // The callback that is invoked by v8 whenever the JavaScript 'print'
2.??????????????? // function is called. Prints its arguments on stdout separated by
3.??????????????? // spaces and ending with a newline.
4.??????????????? v8::Handle<v8::Value> Print(const v8::Arguments& args) {
5.??????????????? ??? bool first = true;
6.??????????????? ??? for (int i = 0; i < args.Length(); i++)
7.??????????????? ??? {
8.??????????????? ??????? v8::HandleScope handle_scope;
9.??????????????? ??????? if (first)
10.??????????? ??????? {
11.??????????? ??????????? first = false;
12.??????????? ??????? }
13.??????????? ??????? else
14.??????????? ??????? {
15.??????????? ??????????? printf(" ");
16.??????????? ??????? }
17.??????????? ??????? //convert the args[i] type to normal char* string
18.??????????? ??????? v8::String::AsciiValue str(args[i]);
19.??????????? ??????? printf("%s", *str);
20.??????????? ??? }
21.??????????? ??? printf(" ");
22.??????????? ??? //returning Undefined is the same as returning void...
23.??????????? ??? return v8::Undefined();
24.??????????? }
這里,為每個參數都構建了v8::String::AsciiValue對象:數據的char*表示。通過它,我們就可以把所有類型都轉換成字符串并打印出來。
JavaScript演示
在演示程序里,我們有一個簡單的JavaScript腳本,調用了迄今為止我們建立的所有東西:
print("begin script");
print(script executed by? + user);
if ( user == "John Doe"){
??? print("/tuser name is invalid. Changing name to Chuck Norris");
??? user = "Chuck Norris";
}
print("123 plus 27 = " + plus(123,27));
x = plus(3456789,6543211);
print("end script");
?
存取C++對象
為我們的類準備環境
如果用C++把一個類映射到JavaScript中去?放一個演示用的類上來先:
1.??????????????? //Sample class mapped to v8
2.??????????????? class Point
3.??????????????? {
4.??????????????? public:
5.??????????????? ??? //constructor
6.??????????????? ??? Point(int x, int y):x_(x),y_(y){}
7.??????????????? ?
8.??????????????? ??? //internal class functions
9.??????????????? ??? //just increment x_
10.??????????? ??? void Function_A(){++x_;??? }
11.??????????? ?
12.??????????? ??? //increment x_ by the amount
13.??????????? ??? void Function_B(int vlr){x_+=vlr;}
14.??????????? ?
15.??????????? ??? //variables
16.??????????? ??? int x_;
17.??????????? };
為了把這個類完全嵌入腳本中,我們需要映射類成員函數和類成員變量。第一步是在我們的上下文中映射一個類模型(class template):
1.??????????????? Handle<FunctionTemplate> point_templ = FunctionTemplate::New();
2.??????????????? point_templ->SetClassName(String::New("Point"));
我們建立了一個"函數"模型[FunctionTemplate],但這里應該把它看成類。
然后,我們通過原型模型(Prototype Template)加入內建的類方法:
1.??????????????? Handle<ObjectTemplate> point_proto = point_templ->PrototypeTemplate();
2.??????????????? point_proto->Set("method_a", FunctionTemplate::New(PointMethod_A));
3.??????????????? point_proto->Set("method_b", FunctionTemplate::New(PointMethod_B));
接下來,類有了兩個方法和對應的回調。但它們目前只在原型中,沒有類實例訪問器我們還不能使用它們。
1.??????????????? Handle<ObjectTemplate> point_inst = point_templ->InstanceTemplate();
2.??????????????? point_inst->SetInternalFieldCount(1);
SetInternalFieldCount函數為C++類建立一個空間(后面會用到)。
現在,我們有了類實例,加入訪問器以訪問內部變量:
1.??????????????? point_inst->SetAccessor(String::New("x"), GetPointX, SetPointX);
接著,“土壤”準備好了,開始播種:
1.??????????????? Point* p = new Point(0, 0);
新對象建立好了,目前只能在C++中使用,要放到腳本里,我們還要下面的代碼:
1.??????????????? Handle<Function> point_ctor = point_templ->GetFunction();
2.??????????????? Local<Object> obj = point_ctor->NewInstance();
3.??????????????? obj->SetInternalField(0, External::New(p));
好了,GetFunction返回一個point構造器(JavaScript方面), 通過它,我們可以用NewInstance生成一個新的實例。然后,用Point對象指針設置我們的內部域(我們前面用SetInternalFieldCount建立的空間),JavaScript可以通過這個指針存取對象。
還少了一步,我們只有類模型和實例,但還缺一個名字來存取它:
1.??????????????? context->Global()->Set(String::New("point"), obj);
在JavaScript里訪問類方法
最后,我們還要解釋一下怎樣在Point類中訪問Function_A...
讓我們看看PointMethod_A回調:
1.??????????????? Handle<Value> PointMethod_A(const Arguments& args)
2.??????????????? {
3.??????????????? ??? Local<Object> self = args.Holder();
4.??????????????? ??? Local<External> wrap = Local<External>::Cast(self->GetInternalField(0));
5.??????????????? ??? void* ptr = wrap->Value();
6.??????????????? ??? static_cast<Point*>(ptr)->Function_A();
7.??????????????? ??? return Integer::New(static_cast<Point*>(ptr)->x_);
8.??????????????? }
和普通訪問器一樣,我們必須處理參數。要訪問我們的類,必須從內部域(第一個)中取得類指針。把內部域映射到"wrap"之后,我們使用它的"value"方法取得類指針。
其它
希望這篇文章對你有所幫助,如果發現文章有誤,請不吝賜教
?
?
總結
- 上一篇: SQL注入原理深度解析
- 下一篇: v8学习笔记(八) 【JS与C++互调】