Skip to content

JavaScript Engine Example

Akash Kava edited this page Oct 13, 2021 · 10 revisions

Synchronous

FastEval is single threaded method that will compile and execute JavaScript, it will not wait for any setTimeout and it will not resolve any promises. It is useful for simple calculations.

var context = new JSContext();

// create global function
context["add"] = new JSFunction((in Arguments a) => {
    return new JSNumber(
         (a[0]?.IntValue ?? 0) + (a[1]?.IntValue ?? 0)
    );
});

var result = context.FastEval("add(4,5)", "script.js");

Asynchronous

In order to use setTimeout or Promise, context needs a SynchronizationContext. So either SynchronizationContext.Current must be non null or you must provide while constructing JSContext.

FastEval

If SynchronizationContext is present, for example if you are invoking script in a UI Thread, then you can use FastEval and convert result into Task and await on it as shown below.

var context = new JSContext();

var r = context.FastEval("some async code", "script.js");

var result = await (r is JSPromise promise).Task;

Eval

In absence of SynchronizationContext you can use Eval method which will execute JavaScript code synchronously along with new SynchronizationContext, and it will return after all setTimeout/setInterval methods or Promises are resolved. This method is not asynchronous.

var context = new JSContext();

var r = context.Eval("some async code", "script.js");

Arguments Object

Every method/function are called as JSFunctionDelegate which is defined as delegate JSValue JSFunctionDelegate(in Arguments a). So basically every JavaScript function receives single readonly struct Arguments. Arguments struct contains This, NewTarget and other arguments passed along. In order to improve performance, struct lives on the stack and only a reference to struct is passed in each method. This reduces unnecessary array allocation for every method call. However less than 4 arguments are passed as field inside struct, all of them live on the stack. Only when actual arguments passed are more than 4, an Array of JSValue is created and passed in Arguments struct.

Create Function

New native C# function can be created with help of JSFunctionDelegate as shown below. It must return a JSValue instance. And you can create JSString, JSNumber from their respective constructors as they are all derived from JSValue. For any other .NET type, object instance can be wrapped in ClrProxy which is derived from JSValue.

Create a global function

Lets create a global function which will add all the numbers passed in.

context["add"] = context.CreateFunction((in Arguments a) => {
    var result = 0.0;
    for(var i = 0; i<a.Length; i++) {
        result += a[i].DoubleValue;
    }
    return new JSNumber(result);
}, "add");

Console.WriteLine(context.FastEval("add(1,2,3)"));

Marshal CLR Object

Custom CLR types can be wrapped in ClrProxy which will allow you to call any methods directly from JavaScript.

context["createUri"] = context.CreateFunction((in Arguments a) => {
    var uri = new Uri(a[0]?.ToString() ?? throw context.NewReferenceError("At least one parameter expected");
    return new ClrProxy(uri);
}, "add");

Console.WriteLine(context.FastEval("var uri = createUri('https://yantrajs.com'); uri.host"));

Naming Convention

All CLR public methods/properties are available in JavaScript as camel case to maintain JavaScript naming convention.

JSModuleContext

To use Modules, you can create JSModuleContext. This context exposes clr module which has following features.

clr.getClass

import clr from "clr";

var int32 = clr.getClass("System.Int32"); // this will return typeof(System.Int32);

new CLR Constructor

Let's see an example of how to use System.Random by creating new instance of it in JavaScript and print next number.

import clr from "clr";
var Random = clr.getClass("System.Random");
var r = new Random(); // this will create instance of `System.Random`.
var n = r.next(); // this will call `Random.Next` method

// n is `number` and not `System.Double`.
assert(typeof n === 'number');

Basic type conversions are as follow.

CLR Type JavaScript
byte, sbyte, short, ushort, int, uint, double, float, number
enum string
long, ulong BigInt
string, char string
IEnumerable iterable
IList array
Task Promise
any other CLR type object (wrapped in ClrProxy)
Clone this wiki locally