V8 JavaScript contines

上次的V8测试代码没有写完,CodeProject 有例子,特转载。http://www.codeproject.com/KB/library/Using_V8_Javascript_VM.aspx

 

 

 

Introduction

Who never wandered how virtual machines work? But better than creating your own virtual machine, what if you can use one done by a big company focusing on improove the speed to the VM? In this article I'll introduce how to use in your application the V8 which is Google's open source JavaScript engine which is inside Chrome - the Google’s new browser.

Background

This code is using the v8 as an embedded library to execute javascript code. To get the source of the library and more information, go to v8 developer page.To effectively use the V8 library you need to know c/c++ and javascript.

Using the Code

Let's explain what is inside the demo. This demo shows:

  • How to use the V8 library api to execute javascript source code.
  • How to access an integer and a string inside the script
  • How to create your own function which can be called inside the script
  • how to create your own c++ class which can be called inside the script 

First let´s understand how to initialize the api. Look at this simple example of embedded V8 on C++:    

#include <v8.h> 
using namespace v8;
int main(int argc, char* argv[]) {
// Create a stack-allocated handle scope. 
HandleScope handle_scope;
// Create a new context. 
Handle<Context> context = Context::New();
// Enter the created context for compiling and 
// running the hello world script.  
Context::Scope context_scope(context);
// Create a string containing the JavaScript source code. 
Handle<String> source = String::New("'Hello' + ', World!'");
// Compile the source code. 
Handle<Script> script = Script::Compile(source);
// Run the script to get the result. 
Handle<Value> result = script->Run();
// Convert the result to an ASCII string and print it. 
String::AsciiValue ascii(result);
printf("%s\n", *ascii);
return 0;
}  

Well, but this doesn't explains how we can control variables and functions inside the script. Ok the script do something... but we need to get this information somehow and have customized functions to control especific behavior inside the script.

The Global Template

First we need a global template to control our modifications:

v8::Handle<v8::ObjectTemplate> global = v8::ObjectTemplate::New();   

This creates a new global template which manage our context and our customizations. This is important as in V8, each context is separated and can have it's own global template.In V8, a context is an execution environment that allows separate, unrelated, JavaScript applications to run in a single instance of V8. 

Adding Customized Functions

After that we can add a new function named "plus":

// plus function implementation - Add two numbers
v8::Handle<v8::Value> Plus(const v8::Arguments& args)
{
unsigned int A = args[0]->Uint32Value();
unsigned int B = args[1]->Uint32Value();
return v8_uint32(A +  B);
}
//...
//associates plus on script to the Plus function
global->Set(v8::String::New("plus"), v8::FunctionTemplate::New(Plus));  

Customized functions need to receive const v8::Arguments& as parameter and must return v8::Handle<v8::Value> . Using the global template pointer created before, we add the function to the template and associates the name "plus" to the callback "Plus". Now each time we use "plus" function on our script, the function "Plus" will be called. The "Plus function do just one thing: get the first and second parameters and return the sum.

Ok, now we can use a customized function inside the javascript side: 

plus(120,44);  

and we can use this function's result inside the script:

x = plus(1,2);
if( x == 3){
// do something important here!
}

Accessors - Variables Accessible inside the Script!

Now we can create functions... but wouldn't be cooler if we can use inside the script something defined outside? Let's do it! the V8 have a thing called Accessor and with it we can associate one variable with a name and two Get / Set functions which will be used by the V8 to access the variable when running the script.

global->SetAccessor(v8::String::New("x"), XGetter, XSetter);   

This associates the name "x" with the "XGetter" and "XSetter" functions. The "XGetter" will be called by the V8 each time the script need the value of "x"variable and the "XSetter" will be called each time the script must update the value of "x".And now the functions:

//the x variable!
int x;
//get the value of x variable inside javascript
static v8::Handle<v8::Value> XGetter( v8::Local<v8::String> name, const v8::AccessorInfo& info) {
return  v8::Number::New(x);
}
//set the value of x variable inside javascript
static void XSetter( v8::Local<v8::String> name, v8::Local<v8::Value> value, const v8::AccessorInfo& info) {
x = value->Int32Value();
} 

In the XGetter we only need to convert the "x" to a number type value managed by the V8.

In the XSetter we need to convert the value passed as parameter to an integer value. There is one function to each basic type (NumberValue for double, BooleanValue for bool, etc.)

Now we can do the same again for string (char pointer) :

//the username accessible on c++ and inside the script
char username[1024];
//get the value of username variable inside javascript
v8::Handle<v8::Value> userGetter(v8::Local<v8::String> name, const v8::AccessorInfo& info) {
return v8::String::New((char*)&username,strlen((char*)&username));
}
//set the value of username variable inside javascript
void userSetter(v8::Local<v8::String> name, v8::Local<v8::Value> value,
const v8::AccessorInfo& info) {
v8::Local<v8::String> s = value->ToString();
s->WriteAscii((char*)&username);
}  

For the string the things changed a little. The "userGetter" creates a new V8 string in the same way the XGetter do but the userSetter need first to access the internal string buffer by using the ToString function. Then using the pointer to the internal string object, we use the WriteAscii function to write it's content to our buffer. Now just add the accessor and voila!

 //create accessor for string username
global->SetAccessor(v8::String::New("user"),userGetter,userSetter); 

Printing

The "print" function is another customized function which catch all parameters and print them using "printf" function. As we have done before with the "plus" function, We register our new function on the global template: 

//associates print on script to the Print function
global->Set(v8::String::New("print"), v8::FunctionTemplate::New(Print)); 

"print" implementation:

// The callback that is invoked by v8 whenever the JavaScript 'print'
// function is called.  Prints its arguments on stdout separated by
// spaces and ending with a newline.
v8::Handle<v8::Value> Print(const v8::Arguments& args) {
bool first = true;
for (int i = 0; i < args.Length(); i++)
{
v8::HandleScope handle_scope;
if (first)
{
first = false;
}
else
{
printf(" ");
}
//convert the args[i] type to normal char* string
v8::String::AsciiValue str(args[i]);
printf("%s", *str);
}
printf("\n");
//returning Undefined is the same as returning void...
return v8::Undefined();
}  

For each parameter, the v8::String::AsciiValue object is constructed to create a char pointer representation of the passed value. With it we can convert other types to it's string representation and print them! 

Sample Javascript

Inside the demo program we have this sample javascript to use what we have created so far:

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");   

This script uses the "x" variable, the "user" variable , the "plus" and "print" functions.

Accessors with C++ Objects!  

v8_demo_object.JPG 

Preparing the environment for our class  

Now how to map a class to the javascript using c++? First the sample class:  

//Sample class mapped to v8
class Point
{
public:
//constructor
Point(int x, int y):x_(x),y_(y){}
//internal class functions
//just increment x_
void Function_A(){++x_;	}
//increment x_ by the amount
void Function_B(int vlr){x_+=vlr;}
//variables
int x_;
}; 

For this class be fully on javascript, we need to map both functions and internal variable. The first step is to map a class template on our context:

Handle<FunctionTemplate> point_templ = FunctionTemplate::New();
point_templ->SetClassName(String::New("Point"));

We create a "function" template but this can be seen as class. This template will have the "Point" name for later usage (like create new instances of it inside the test script)

 Then we access the prototype class template to add build in methods for our class:  

Handle<ObjectTemplate> point_proto = point_templ->PrototypeTemplate();
point_proto->Set("method_a", FunctionTemplate::New(PointMethod_A));
point_proto->Set("method_b", FunctionTemplate::New(PointMethod_B));

After this the class "knows" that it has two methods and callbacks. But this is still in the prototype , we can't use it without accessing an instance of the class. 

Handle<ObjectTemplate> point_inst = point_templ->InstanceTemplate();
point_inst->SetInternalFieldCount(1);

The SetInternalFieldCount function creates space for the c++ class pointer (will see this later) 

Now we have the class instance and can add accessors for  the internal variable  

 point_inst->SetAccessor(String::New("x"), GetPointX, SetPointX);

Then we have the "ground" prepared... throw the seed:

 Point* p = new Point(0, 0);

The new class is created but only can be acessed on c++. To access we need:

Handle<Function> point_ctor = point_templ->GetFunction();
Local<Object> obj = point_ctor->NewInstance();
obj->SetInternalField(0, External::New(p));

Well, GetFunction return the point constructor (on the javascript "side") and with it we can create a new instance using  NewInstance. Then we set our internal field (which we have created "space" for with SetInternalFieldCount) with the class pointer. With that, the javascript will have access to the object through the  pointer.

 Only one step is missing. We only have a class template and instance, but no name to access it from the javascript: 

 context->Global()->Set(String::New("point"), obj);

This last step link the "point" name to the instance obj. With these steps we just emulates the creation of class Point with name point in our script (before loading or running it!).

Accessing class method inside the javascript 

Ok, but this doesn't explain how we can access the  Function_A inside the Point class...

Let's look at the  PointMethod_A callback: 

 Handle<Value> PointMethod_A(const Arguments& args)
{
Local<Object> self = args.Holder();
Local<External> wrap = Local<External>::Cast(self->GetInternalField(0));
void* ptr = wrap->Value();
static_cast<Point*>(ptr)->Function_A();
return Integer::New(static_cast<Point*>(ptr)->x_);
} 

Like a normal accessor, we must handle the arguments. But to have access to our class, we must get the class pointer from the internal field (first one). After mapping the internal field to "wrap" we get the class pointer by retrieving its "value". Now it's a matter of cast...

Now we have the class pointer for the "point" class being used (which called method_a, which called the callback PointMethod_A).

The sample  v8_embedded_demo_with_object.zip implements all this steps. 

I hope this helps and if something is wrong or unclear, please don't hesitate to contact.

posted @ 2008-09-28 10:27  Marvin  阅读(1273)  评论(1编辑  收藏  举报