DEV Community

Akash Kava for Web Atoms

Posted on

Embed V8 JavaScript Engine in Xamarin.Android

Isn't there any existing JavaScript engine for Xamarin.Android?

Well, there is LiquidCore project, which has Java bindings for V8, you can use it to embed V8 in Xamarin.Android by natively embedding Android Native Project (AAR) files of LiquidCore project.

Also there are other implementations such as DuckTape, JavaScriptCore etc.

The problem with all of them is, your C# code actually calls (Marshals all parameters) Java code which in turn calls (Marshals all parameters) again to V8.

   // Marshall all parameters 
   CLR Code -> Java Code -> V8 Native

   // Marshal result
   CLR Code <- Java Code <- V8 Native

Enter fullscreen mode Exit fullscreen mode

This in turn slows your execution if your code frequently access C# code.

So I decided to remove "Java" bridge to call V8.

V8 is Embedded as a Native Library

Since V8 is embedded as native library, calls from C# does not go through Java bride.

   CLR Code -> V8 Native
   CLR Code <- V8 Native
Enter fullscreen mode Exit fullscreen mode

V8 Inspector Protocol Support

Currently LiquidCore does not support Inspector protocol, that means you cannot debug your JavaScript. And other libraries such as DuckTape etc does not have any support for debugging at all.

Xamarin.Android.V8 (Name is subjected to change in future)

GitHub logo web-atoms / xamarin-v8

V8 Bindings for Xamarin for Android

NuGet

Xamarin V8 Bindings

V8 Bindings for Xamarin for Android

Though V8 is very heavy, you can try YantraJS, very light weight JavaScript engine written in C#.

NuGet

<PackageReference Include="Xamarin.Android.V8" Version="1.4.79" />
Enter fullscreen mode Exit fullscreen mode

Inspector Protocol Port

Visual Studio > Tools > Android > Android Adb Command Prompt

adb forward tcp:9222 tcp:9222

If you want to change the default 9222 port, you can specify in the parameters.

Create Context

using Xamarin.Android.V8;

using(var context = new JSContext( /*Enable Debugging*/ true)) {

  // you can connect to dev tools by visiting url
  // devtools://devtools/bundled/inspector.html?experiments=true&v8only=true&ws=127.0.0.1:9222/backend
  
  

}
Enter fullscreen mode Exit fullscreen mode

Create New Global Function

context["printf"] = context.CreateFunction(0, (c, a) => {
  // first parameter is context isself
  // second parameter is an array as IJSValue
  System.
Enter fullscreen mode Exit fullscreen mode

NuGet Package

NuGet Package is available with ID Xamarin.Android.V8

Inspector Protocol Port

To open Inspector protocol port from your device, you have to click Visual Studio > Tools > Android > Android Adb Command Prompt. Then type,

    adb forward tcp:9222 tcp:9222
Enter fullscreen mode Exit fullscreen mode

Create Context

using(var context = new JSContext( /*Enable Debugging*/ true)) {

  // you can connect to dev tools by visiting url
  // devtools://devtools/bundled/inspector.html?experiments=true&v8only=true&ws=127.0.0.1:9222/backend

    context.Evaluate("console.log('I am ready')", "vm");

}
Enter fullscreen mode Exit fullscreen mode

Now you have full debugging support in the given link

devtools://devtools/bundled/inspector.html?experiments=true&v8only=true&ws=127.0.0.1:9222/backend
Enter fullscreen mode Exit fullscreen mode

Create New Global Function

Context itself is a global object. So you can store/retrive values on it.

context["printf"] = context.CreateFunction(0, (c, a) => {
  // first parameter is context isself
  // second parameter is an array as IJSValue
  System.Diagnostics.Debug.WriteLine(a[0].ToString());
  return c.Undefined;
});

context.Evaluate("  printf('This is from JS Coe'); ", "vm");

// this works as well
context.Evaluate("  global.printf('This is from JS Coe'); ", "vm");
Enter fullscreen mode Exit fullscreen mode

Evaluate Script with Location

// script location is useful for debugging
context.Evaluate(scriptText, scriptLocation);
Enter fullscreen mode Exit fullscreen mode

Navigate Objects

Each JavaScript object is exposed to CLR as IJSValue, you can access properties and methods on this object directly.

   // Object.create() JavaScript Equivalent in c#
   var obj = context["Object"].InvokeMethod("create");

Enter fullscreen mode Exit fullscreen mode

   var obj = context.CreateObject();
   obj["name"] = context.CreateString("Akash");

   // Object.keys(obj) JavaScript Equivalent in c#
   var keys = context["Object"].InvokeMethod("keys", obj);

   for(var i = 0; i < keys.Length; i++) {
       var key = keys[i].ToString();
       var value = obj[key].ToString();

       //... you have key and value here
   }

Enter fullscreen mode Exit fullscreen mode

Serialize C# Object

When you use method context.Convert method to automatically create native JS values from native types, it will only wrap C# custom object, you cannot call any method or access property from JavaScript on wrapped object. This is done to improve performance. So when you pass C# objects in and out, engine will not create methods and properties on them.

In order to access methods and properties of C# object, you have to serialize them.

  // you can access all properties, no methods
   var jsDictObject = context.Serialize( customClrObject , SerializationMode.Copy);

   // you can access all properties and invoke method as well
   var jsClrObject = context.Serialize( customClrObject , SerializationMode.Reference);
Enter fullscreen mode Exit fullscreen mode

Serialization Modes

Copy

This method will create a deep copy of CLR Object as dictionary which you can easily access inside JavaScript code. This method will fail if there are self referencing objects in the object graph. This limitation may be removed in future, but right now it will throw an exception.

This method is also very slow as deep copy operation will take more time.

Deserialization will also be slow as it will completely construct new object with all properties.

Reference

Keeps reference along with serialization, every property is serialized as getter/setter, upon deserialization, same object will be returned.

This method is useful for self referencing objects, but this may cause memory leak if you keep reference in JavaScript and JavaScript garbage collector fails to dispose object.

Deserialization is faster as it simply returns referenced object.

WeakReference

Same as Reference but it only keeps weak reference, you will get object disposed if you try to access object in JavaScript and it is disposed in CLR. CLR is very aggressive while disposing objects, so this may not work if you do not keep reference in CLR. This is also recommended method as it will avoid memory leaks.

Wrap

This is default serialization method for any object. Object will simply be wrapped and no methods/properties are exposed.

Thread Safety

This library is not thread safe and it is recommended that you use JSContext on the same thread as you created on. Most likely it is better to call in UI Thread.

This is done to avoid unnecessary locking when you don't need them. You can achieve locking in the CLR when you need them.

License

MIT License

Big Thanks to

A huge thanks to following github repo to help making this project a reality.

  1. https://github.com/LiquidPlayer/LiquidCore
  2. https://github.com/Kudo/v8-android-buildscripts
  3. https://github.com/rjamesnw/v8dotnet

Top comments (0)