skip to Main Content

I have big JavaScript-library that I want to use within my C#-project and it works like a charm using the Microsoft.ClearScript.V8-nuget and the following code:

public int InvokeJavascript(int parameter)
{
    string libraryCode = System.IO.File.ReadAllText("library.js");
    string libraryInvokeCode = $"inoke({parameter})";
    string javascriptCode = $"{libraryCode};{libraryInvokeCode}";
    using (var engine = new V8ScriptEngine())
    {
        var result = engine.Evaluate(javascriptCode);
        return int.Parse(result.ToString());
    }
}

The problem is, that the performance is really poor. I would love to compile the code for the JavaScript Library only once and the invoke on the compiled script to avoid compiling it with every invoke.

Do you have any idea how I can do that?

2

Answers


  1. You may utilize V8ScriptEngine.Compile method and host parameter like this

    using (var engine = new V8ScriptEngine())
    {
        var input = new Input();
        engine.AddHostObject("input", input);
        using (var script = engine.Compile("function invoke(i) {return i;}; invoke(input.i)"))
        {
            for (int i = 0; i < 10; i++)
            {
                input.i = i;
                var result = engine.Evaluate(script);
                Console.WriteLine(result);
            }
        }
    }
    
    public class Input
    {
        public int i { get; set;}
    }
    

    remeber to keep script and engine not disposed, for example you can nicely wrap it around some class like this:

    public class JSExecutor<T, TResult>  : IDisposable
    {
        private readonly V8ScriptEngine engine;
        private readonly V8Script script;
        private readonly Input input;
        
        public JSExecutor(string code)
        {
            engine = new V8ScriptEngine();
            input = new Input();
            engine.AddHostObject("input", input);
            script = engine.Compile(code);
        }
        
        public TResult Execute(T parameter)
        {
            input.parameter = parameter;
            return (TResult)engine.Evaluate(script);
        }
        
        public void Dispose()
        {
            script.Dispose();
            engine.Dispose();
        }
    
        public class Input
        {
            public T parameter { get; set; }
        }
    }
    

    and use it like this:

    using (var executor = new JSExecutor<int,int>("function invoke(i) {return i;}; invoke(input.parameter)"))
    {
        for (int i = 0; i < 10; i++)
        {
            Console.WriteLine(executor.Execute(i));
        }
    }
    
    Login or Signup to reply.
  2. You’re re-reading the library file, re-instantiating the script engine, and re-executing the library code for each call. Caching the engine lets you make multiple calls without all that overhead:

    public sealed class JavaScriptInvoker : IDisposable {
        private V8ScriptEngine _engine = new V8ScriptEngine();
        private string _functionName;
        public JavaScriptInvoker(string libraryCode, string functionName) {
            _engine.Execute(libraryCode);
            _functionName = functionName;
        }
        public object Invoke(params object[] args) => _engine.Invoke(_functionName, args);
        public void Dispose() => _engine.Dispose();
    }
    

    Usage example:

    const string libraryCode = "function invoke(a, b, c) { return a + b + c; }";
    using (var invoker = new JavaScriptInvoker(libraryCode, "invoke")) {
        Console.WriteLine(invoker.Invoke(123, 456, 789));
        Console.WriteLine(invoker.Invoke("foo ", "bar ", "baz"));
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search