This may be because I am currently working as Mobile JS Engineer, but there is really something about JS that makes me smile. Something about the way the code flows and how I have to think to get things done makes me feel like I am achieving something amazing – even when all I am doing is pooping out a stupid express app to maintain a user list or something.
Either way, Integrating V8 is NOT as straight forward as I would like. As such, here is what I had to do to get V8 to integrate into a C++ application I am working on. Be aware that this is not for the faint of heart, it will take some determination if you are a novice.
And if you aren’t a novice – what the hell are you doing reading tutorials!?
svn checkout http://v8.googlecode.com/svn/trunk/ v8
git clone git://github.com/v8/v8.git v8
Once you are fully cloned it is time to get into the guts of the process. First things first, get into that directory and you will need to checkout four more libraries – gyp, python2.6, icu and cygwin. V8 uses gyp to build out the libraries and handle the linking, python is required to run gyp, cygwin is required for some other dependencies and icu is an internationalization library that V8 leverages.
> cd v8 > svn co http://gyp.googlecode.com/svn/trunk build/gyp > svn co http:[email protected] third_party/python_26 > svn co http:[email protected] third_party/cygwin > svn co https://src.chromium.org/chrome/trunk/deps/third_party/icu46 third_party/icu
Next you will need to generate your project & solution files. I targeted x64 for my build, you may target anything you wish
> third_party/python_26/python.exe build\gyp_v8 -Dtarget_arch=x64
With that you have enough in place to be able to build the libraries, with one caveat. The project files that are generated are configured for building in MSVS 2010, and Microsoft has graced us with some very important warnings…
- Each version of MSVS is bound to a different version of the c++ compiler, bundled with the install
- More importantly, binding a library compiled with a different version of the compiler does not work
So this is when things get fun and start branching. I am working on a project that is built in MSVS 2012 and so there is an upgrade process needed. Open up the solution in the MSVS of your choosing, and once all of the projects are loaded (or as MSVS attempts to load them) it will prompt you to see if you would like to upgrade immediately. If you missed this prompt and or skipped it you are free to upgrade your projects via the Project > Update VC++ project menu option.
From this point you can build your libraries to your heart’s content, just remember to differentiate between debug and release modes. You will want to be using release mode libraries when you finally release your application.
You may want to ensure a couple of other configurations are set, as they gave me a bit of problem when I was playing with things:
- V8 needs to be configured to link as a static library, so open up the properties for the V8 Projects and ensure that Configuration Properties > General > Project Defaults > Configuration Type should be set to
Static library (.lib)
- You will need to target the same type of runtime library as well, so please ensure your Configuration Properties > C/C++ > Code Generation > Runtime Library is configured as your application is. Mine is set to
Multi-threaded DLL (/MD)
Linking the libraries
When you compile things out the library files will land in the build\Release\lib directory, and the includes you will need to reference reside in the include directory, off of your base V8 directory. You will likely want to copy these to your application’s build or lib path for versioning and maintenance. Remember: one of the reasons that you checked all of this out instead of downloading prebuilt binaries (or at least libraries in this situation) is to have:
- the ability to update when V8 is updated and
- so you can target your own implementation
Copy over the contents of both folders, to your applications directory. I used %BetterTech%/third_party/v8_v.3.24/ where I made Libraries and Includes folders for examples sake. You will need to add these to your project’s include and libraries paths, which can be updated by going to Configuration Properties > VC++ Directories and adding in your entries. These are used similar to the %PATH% environment variable to lighten the load of referring to your libraries. Otherwise you would need to refer to the libraries by their full/absolute paths for the project to compile, which is no good.
Now you should reference the libraries, there are four that I am using – icui18n.lib, icuuc.lib, v8_base.x64.lib, v8_nosnapshot.x64.lib and there are two routes to doing this. The first way you can use is to continue using your application’s properties page, Configuration Properties > General > Linker > Input and add each of the libraries there. This will absolutely work, but it makes it a bit more involved to see where your classes are actually using this library, so lets look at a second way.
You can actually include libraries, even by full paths, via a #pragma statement in your cpp file, which in my eyes is supportive of encapsulation, while playing around in this wizard like interface can be frustrating at times, and not to mention it but it will also look pretty neat. In the next section we will also need a #pragma statement, so keep your wits about you!
Create a new class – mine is called V8Binding, and in its header add the following:
#pragma comment(lib, "icui18n.lib") #pragma comment(lib, "icuuc.lib") #pragma comment(lib, "v8_base.x64.lib") #pragma comment(lib, "v8_nosnapshot.x64.lib")
This tells the compiler to attempt to find and link in these libraries, as long as they are in the libraries paths we added in earlier. Building at this time will exercise our code and should create no errors. If you run into any make sure that you comment in below and make some time where you are available so maybe I can help you out directly.
As I said though, this is not for the faint of heart.
First we will need to include the v8 header file:
Now we are able to reference the V8 elements that we linked in earlier.
We need to make a quick pitstop though, to travel through some V8 setup and configuration details. V8 is a pretty great implementation of the JS standard, and it has to be configured correctly to be able to interact across the boundary between C++ and JS.
- V8 calls its JS VMs Isolates, which has its own heap, and all of the contexts and such you will be creating.
- V8 uses Handles to reach across to JS objects and elements from C++, this is because V8 has its own garbage collector and these handles maintain reference counting and such.
- You will define your context, defining global elements for it, and then enter it.
With these details in mind, lets get into some fun:
If you are a monster about programming, like I absolutely am, and you run your code in strict mode, or with warnings as errors, you will run into a problem in about 12 seconds.
Add this in to your cpp file:
The following block of code is used to initialize the V8 library for use, and can put this in any function, just make sure that when you instantiate your binding it is called.
v8::V8::InitializeICU(); v8::Isolate *isolate = v8::Isolate::GetCurrent(); // JS VM created v8::HandleScope handle_scope(isolate); // Create an isolate scope v8::Handle global<v8::ObjectTemplate> = v8::ObjectTemplate::New(isolate); global->Set(v8::String::NewFromUtf8(isolate, "print"), v8::FunctionTemplate::New(isolate, Print)); v8::Handle<v8::Context> context = v8::Context::New(isolate, NULL, global); context->Enter();
We have created a global function, print, which is bound to a function in our Application, specifically to a global function that outputs its values to stdout. Now we can execute JS.
Warnings as errors!? Oh noes!
If you compiled the previous code with warnings treated as errors you are likely to have run aground. There is a reinterpret_cast call being used on the set method that is not exactly correct, according to the compiler at least. The code is fine, however, as it is nearly identical to the sample provided by Google. To fix it we will be ignoring this warning explicitly.
Around the call to include the v8 header you will need to wrap it in pragma calls to ignore this warning.
// Disable warning messages 4946 - reinterpret_cast of similar type // This is done to allow for v8 Initialization #pragma warning( push ) #pragma warning( disable : 4946 ) #include "v8.h" // Resume standard warnings #pragma warning( pop )
Now to execute some JS code
To be able to execute the code we will need to define a couple things. First, the source code itself needs to be defined, as a v8::String, then a name will need to be defined, im not exactly sure what this does but its needed. Once those are defined you are going to following a common process:
- Compile the script out through the v8 runtime
- Run the compiled script, the result of running it is returned as a handle.
- Convert the output to a cString if you wish and do whatever you wish with it.
v8::Handle<v8::Source> = v8::String::NewFromUtf8(context->GetIsolate(), "print(42);"); v8::Handle<v8::Name> = v8::String::NewFromUtf8(context->GetIsolate(), "(shell)"); v8::Handle<v8::Script> script = v8::Script::Compile(source, name); v8::Handle<Value> result = script->Run(); v8::String::Utf8Value str(result); const char* cstr = ToCString(str); printf("%s\n", cstr);
For the purposes of this example I am calling the print function defined above, which does not return anything. Lets see what happens when we run it.
> BetterTech.exe 42 > _
Where to go from here
All and all, this was pretty interesting and I found the process, while complicated and clearly quite technical, to be a lot of fun. At this point I can expose any object in my application to the JS world and it can modify the contents of values, execute events and call functions back and forth. I will think about doing a second installment of this where I show the ability to reach back and forth, but now its time to get back to development of BetterTech.
As always, Questions and/or Comments below.