I recently started working on a new project and part of it is about embedding V8 JavaScript engine. So far, my experience with V8 is very good. The object model is nice and clean and although the lack of a good documentation it is easy to work with it. I strongly recommend using V8, but in this post I am going to show you another option.
After I gained some know-how with V8, I decided to explore IE 11 Chakra JavaScript engine. Last week Microsoft announced IE 11 availability for Windows 7 and I guess it will become a good alternative to V8. I guess using Chakra engine may be tempting for developers that do not want to build V8 from source code or if they want to avoid static linking in order to keep small executable files.
For the sake of this introductory post, I am going to show you how to implement a simple printf-like functionality with Chakra (please note that I am going to provide just an overview, for more details see [1]). Suppose we want to implement a very simple print function that accepts some format string, an integer and a string and formats the output as shown.
native.printf('number=%#x string=%s\n', 255, 'test')
We are going to embed Chakra engine in a simple console app that runs this script and just outputs the result.
[Note: for the sake of brevity I am going to omit the error handling. The source code contains all the error checks though.]
The first thing we have to do is to create new Chakra JavaScript runtime. The runtime represents a complete JavaScript execution environment and has a single thread of execution.
JsRuntimeHandle runtime; JsCreateRuntime(JsRuntimeAttributeNone, JsRuntimeVersion11, nullptr, &runtime);
Once we have created a runtime we have to create an execution context. There can be multiple execution contexts that are active on a thread at the same time.
JsContextRef context; JsCreateContext(runtime, nullptr, &context); JsSetCurrentContext(context);
Now, it is time to execute the script. This is done via JsRunScript
function.
wstring script(L"native.printf('number=%#x string=%s\\n', 255, 'test')"); JsValueRef result; JsSourceContext contextCookie = 0; JsRunScript(script.c_str(), contextCookie, L"source", &result);
Right now, you are probably guessing where this native.prinft
thing would come from. That’s right, I missed that part on purpose because I want to show the very basic workflow:
- create a runtime
- create a context
- run the script
Let’s see what is needed to make native.printf
to work. Every JavaScript runtime environment has one root object called global. This global object is the object that holds all top-level objects. So in our case we have to create a new object and make it accessible through native
property on the global object. Then we have to create another object, actually a function, and make it accessible through printf
property on the native
object.
JsValueRef global; JsGetGlobalObject(&global); JsPropertyIdRef nativeProp; JsGetPropertyIdFromName(L"native", &nativeProp); JsValueRef nativeObj; JsCreateObject(&nativeObj); JsPropertyIdRef printfProp; JsGetPropertyIdFromName(L"printf", &printfProp); JsValueRef printfFunc; JsCreateFunction(PrintFormat, nullptr, &printfFunc); JsSetProperty(nativeObj, printfProp, printfFunc, true); JsSetProperty(global, nativeProp, nativeObj, true);
The final missing thing is the PrintFormat
callback function.
JsValueRef CALLBACK PrintFormat(JsValueRef callee, bool isConstructCall, JsValueRef *arguments, unsigned short argumentCount, void *callbackState) { const wchar_t *format; size_t length; JsStringToPointer(arguments[1], &format, &length); VARIANT variant; JsValueToVariant(arguments[2], &variant); const wchar_t *str; JsStringToPointer(arguments[3], &str, &length); wprintf(format, variant.intVal, str); return JS_INVALID_REFERENCE; }
That’s all. We implemented all the functionality required to execute native.printf
function.
In closing I would say that using Chakra engine is fairly easy. The API is not object-oriented and some less experienced developers may find it as a drawback. On the other hand it is easy the incorporate Chakra C-style API in existing OO code base or use it from a script language like Python.
Further reading: