Introduction
Who never wondered 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 improving the speed to the VM? In this article, I'll introduce how to use the V8 in your application, which is Google's open source JavaScript engine inside Chrome - Google’s new browser.
Background
This code uses the V8 as an embedded library to execute JavaScript code. To get the source of the library and more information, go to the V8 developer page. To effectively use the V8 library, you need to know C/C++ and JavaScript.
Using the Code
Let's see what is inside the demo. The 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[]) {
HandleScope handle_scope;
Handle<Context> context = Context::New();
Context::Scope context_scope(context);
Handle<String> source = String::New("'Hello' + ', World!'");
Handle<Script> script = Script::Compile(source);
Handle<Value> result = script->Run();
String::AsciiValue ascii(result);
printf("%s\n", *ascii);
return 0;
}
Well, but this doesn't explain how we can control variables and functions inside the script. OK, the script does something... but, we need to get this information somehow, and have customized functions to control specific 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 manages our context and our customizations. This is important because in V8, each context is separated, and can have its 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
":
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);
}
global->Set(v8::String::New("plus"), v8::FunctionTemplate::New(Plus));
Customized functions need to receive const v8::Arguments&
as a 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 the "plus
" function on our script, the function "Plus
" will be called. The Plus
function does just one thing: get the first and the second parameters and return the sum.
OK, now we can use a customized function in the JavaScript side:
plus(120,44);
and we can use this function's result inside the script:
x = plus(1,2);
if( x == 3){
}
Accessors - Variables Accessible Inside the Script!
Now, we can create functions... but wouldn't it be cooler if we can use something defined outside inside the script? Let's do it! The V8 have a thing called Accessor, and with it, we can associate a 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 V8 each time the script needs the value of the "x
" variable, and the "XSetter
" will be called each time the script must update the value of "x
". And now, the functions:
int x;
static v8::Handle<v8::Value> XGetter( v8::Local<v8::String> name,
const v8::AccessorInfo& info) {
return v8::Number::New(x);
}
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 "x
" to a number type value managed by 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*
):
char username[1024];
v8::Handle<v8::Value> userGetter(v8::Local<v8::String> name,
const v8::AccessorInfo& info) {
return v8::String::New((char*)&username,strlen((char*)&username));
}
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, things changed a little. The "userGetter
" creates a new V8 string in the same way the XGetter
does, but the userSetter
needs 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 its content to our buffer. Now, just add the accessor, and voila!
global->SetAccessor(v8::String::New("user"),userGetter,userSetter);
Printing
The "print
" function is another customized function which catches all parameters and prints them using the "printf
" function. As we have done before with the "plus
" function, we register our new function on the global template:
global->Set(v8::String::New("print"), v8::FunctionTemplate::New(Print));
"print" implementation
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(" ");
}
v8::String::AsciiValue str(args[i]);
printf("%s", *str);
}
printf("\n");
return v8::Undefined();
}
For each parameter, the v8::String::AsciiValue
object is constructed to create a char*
representation of the passed value. With it, we can convert other types to their 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, and the "plus
" and "print
" functions.
Accessors with C++ Objects!
Preparing the Environment for our Class
Now, how to map a class to the JavaScript using C++? First, the sample class:
class Point
{
public:
Point(int x, int y):x_(x),y_(y){}
void Function_A(){++x_; }
void Function_B(int vlr){x_+=vlr;}
int x_;
};
For this class to be fully on JavaScript, we need to map both the 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 a 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 built-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 can only be accessed in C++. To access it, we need:
Handle<Function> point_ctor = point_templ->GetFunction();
Local<Object> obj = point_ctor->NewInstance();
obj->SetInternalField(0, External::New(p));
Well, GetFunction
returns 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 links the "point
" name to the instance obj
. With these steps, we just emulate the creation of class Point
with the name point
in our script (before loading or running it!).
Accessing a Class Method Inside the JavaScript
OK, but this doesn't explain how we can access 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 these steps.
I hope this helps, and if something is wrong or unclear, please don't hesitate to contact.
Points of Interest
Before trying to use the V8 library, I've tried to use Lua as an embedded scripting language to support easy configuration and changes by the user. I got amazed by V8 as it's faster and easier to use than Lua to implement scripting functionality in my own software. I hope this article help others in this task.
Look also for Google's documentation on the V8 library!
History
- 9/5/2008 - First implementation.
- 9/8/2008 - Lua vs. V8 benchmark and sample using dynamic variables.
- 9/11/2008 - Lua benchmark updated (version 2), and finally, object access inside the JavaScript.