Ian is an Eclipse committer and EclipseSource Distinguished Engineer with a passion for developer productivity.
He leads the J2V8 project and has served on several …
Earlier this year Microsoft released ChakraCore – their powerful next generation JavaScript engine – as an OpenSource project under an MIT license. Microsoft also announced that they would be integrating Chakra with Node.js on Windows.
At EclipseSource we have a long tradition of bridging technologies. We brought Eclipse/RCP to the web via RAP; we brought JavaScript as a native platform to mobile devices with Tabris.js; and we brought V8 to Java with J2V8. With our most recent announcement that Tabris.js will now support Windows 10, and the Universal Windows Platform, we have once again found ourselves bridging technology stacks. This time we integrated Microsoft Chakra with C#. In this post I will outline how we developed this bridge, and how you can embed ChakraCore in your own Windows applications.
To get started, clone ChakraCore from GitHub. You can open the project in Visual Studio and build the DLL for your desired architecture. Once built, you can reference the DLL directly from your application. In my case I created a C# Console Application and added the ChakraCore DLL to the Reference Paths.
To reference functions from a DLL, the Platform Invocation Services (PInvoke) was used. Microsoft provides a set of bindings in their Chakra-Samples project that can be used to expose Chakra functionality in C#. To get started, you can simply use these. The most interesting class is Native.cs which lists most of the embedding functionality.
By embedding Chakra in your C# applications you can invoke JavaScript directly from C#, manipulate the Global scope (and any thing accessible from this scope), invoke JavaScript functions and register C# callbacks. This can all be performed without loading or requiring a full blown web-browser. During the rest of this tutorial we will cover these items in detail.
Before you can begin interacting with Chakra as a JavaScript engine from within your C# code, you must first create a JavaScript runtime and context.
JavaScriptRuntime runtime;
JavaScriptContext context;
JavaScriptSourceContext currentSourceContext =
JavaScriptSourceContext.FromIntPtr(IntPtr.Zero);
Native.JsCreateRuntime(JavaScriptRuntimeAttributes.None, null, out runtime);
Native.JsCreateContext(runtime, out context);
Native.JsSetCurrentContext(context);
This will create a runtime, create an execution context and set the execution context as being the current one on this thread. With the setup in place, you can now execute JavaScript programs on the Chakra JavaScript engine.
Once a JavaScript runtime and execution context has been created, you can now execute scripts using the JsRunScript function. The script can be passed as a string, and the result of script returned as a JavaScriptValue. A URL / Filename can also be specified to assist with debugging and stacktraces.
JavaScriptValue result;
string script = "var x = 'hello, world!';\n"
+"x;\n";
Native.JsRunScript(script, currentSourceContext++, "filename.js", out result);
This particular script will set the variable x to the string hello, world! and return that string to C# as a JavaScriptValue.
Passing data between JavaScript and C# is done using JavaScriptValues. Each JavaScriptValue can be inspected for its type, and they can be converted to C# primitives.
JavaScriptValueType jsType;
Native.JsGetValueType(value, out jsType);
Chakra has several functions for converting JavaScriptValues to primitives such as Native.JsNumberToInt and Native.JsBooleanToBool. Finally, JavaScript Strings can be converted to C# Strings with a simple utility method:
using System.Runtime.InteropServices;
public static string JsValueAsString(JavaScriptValue value) {
IntPtr stringValue;
UIntPtr stringLength;
Native.JsStringToPointer(value, out stringValue, out stringLength);
return Marshal.PtrToStringUni(stringValue);
}
Complex types, such as JavaScript Objects and Arrays can be converted to C# objects by writing a deep copy routine. To implement this, first get all the keys from the JavaScript Object:
public static string[] GetKeys(JavaScriptValue jsObject)
JavaScriptValue propertyNames;
Native.JsGetOwnPropertyNames(jsObject, out propertyNames);
int length = GetArrayLength(propertyNames);
string[] keys = new string[length];
for (int i = 0; i < length; i++) {
string key = JsValueAsString(GetJsPropertyAtIndex(propertyNames, i));
keys[i] = key;
}
return keys;
}
private static JavaScriptValue GetJsPropertyAtIndex(JavaScriptValue jsObject, int index) {
JavaScriptValue result;
JavaScriptValue jsIndex;
Native.JsIntToNumber(index, out jsIndex);
Native.JsGetIndexedProperty(jsObject, jsIndex, out result);
return result;
}
Loop through the keys, and get the property values using Native.JsGetProperty(jsObject, key, out value);. For complex types, you will need to perform this operation recursively.
Chakra also supports several functions for converting from C# primitives back to JavaScript types such as Native.JsIntToNumber and Native.JsBoolToBoolean. You can also create a JavaScript Object using Native.JsCreateObject and Native.JsCreateArray, and add properties to the object using Native.JsSetProperty.
So far we’ve demonstrated how to execute scripts and work with the return value. Since you can also create JavaScript objects in C#, with Chakra you can access the Global Scope (and anything reachable from there) and inject JavaScript Objects into the runtime. This pattern allows you to construct complex data object in C#, pass them to Chakra and then execute JavaScript to operate on this data. To access the global scope, use Native.JsGetGlobalObject. This will return a JavaScriptValue. From here you can access the properties on this object, or set new ones – making them available in the global scope.
In addition to manipulating the global scope and invoking scripts, you can also invoke JavaScript functions directly from C#. JS Functions can be called by specifying the JavaScript Object that contains the function (or the global scope), the name of the Function, the context (value of this in JavaScript) and a list of arguments to pass to the function. The context should be the first argument in the list. Here is an example of how to call a JavaScript function from C#.
public static JavaScriptValue CallJsFunction( JavaScriptValue jsObject,
String functionName,
JavaScriptValue context,
JavaScriptValue[] args ) {
JavaScriptValue result;
JavaScriptValue function = GetValue(jsObject, functionName);
JavaScriptValue[] jsFunctionArgs = new JavaScriptValue[args.Length + 1];
jsFunctionArgs[0] = context;
for (int i = 0; i < args.Length; i++) {
jsFunctionArgs[i + 1] = args[i];
}
JavaScriptErrorCode errorCode =
Native.JsCallFunction(function, jsFunctionArgs, (ushort)jsFunctionArgs.Length, out result);
if (errorCode != JavaScriptErrorCode.NoError) {
throw new JSException(errorCode, GetExceptionAndClear());
}
return result;
}
Finally, no deep integration between C# and JavaScript would be complete without the ability to invoke C# methods from JavaScript. This is useful for callbacks and to access C# functionality directly from JavaScript. Delegate functions in C# should have the following signature:
delegate JavaScriptValue JavaScriptNativeFunction( JavaScriptValue callee,
bool isConsructCall,
JavaScriptValue[] arguments,
ushort argCount,
IntPtr data )
Callbacks are created by first wrapping them in a JavaScript function using:
JavaScriptValue reference;
Native.JsCreateFunction(callback, data, out reference);
The reference (a JavaScript function) can then be used wherever a JavaScript function could be. However, when the function is invoked, the C# delegate will be called instead. Note: References to delegates will be garbage collected if they are only referenced outside the managed runtime. That is, if a delegate is only referenced by Chakra, the object reference will be removed. To solve this, we kept a handle to all delegates in C#.
JavaScript is one of the most widely used programming languages in the world. By integrating JavaScript with existing technology stacks, such as C#, you can provide a migration strategy for large C# applications looking to leverage JavaScript.
For more information on bridging JavaScript technologies, follow me on Twitter.
Ian is an Eclipse committer and EclipseSource Distinguished Engineer with a passion for developer productivity.
He leads the J2V8 project and has served on several …