Taritsyn / JavaScriptEngineSwitcher

JavaScript Engine Switcher determines unified interface for access to the basic features of popular JavaScript engines. This library allows you to quickly and easily switch to using of another JavaScript engine.
Apache License 2.0
440 stars 49 forks source link

Send HostObjects as arguments to CallFunction #26

Closed MattMinke closed 5 years ago

MattMinke commented 7 years ago

It would be nice to be able to send host objects as arguments to CallFunction. So for example I would like to be able to do something like this:

class Program
{
    static void Main(string[] args)
    {
        var settings = new ChakraCoreSettings()
        {
            DisableEval = false,
            DisableBackgroundWork = true,
            DisableNativeCodeGeneration = false,
            EnableExperimentalFeatures = false
        };

        JsEngineSwitcher.Instance.EngineFactories.AddChakraCore(settings);
        JsEngineSwitcher.Instance.DefaultEngineName = ChakraCoreJsEngine.EngineName;
        using (var engine = JsEngineSwitcher.Instance.CreateDefaultEngine())
        {
            engine.EmbedHostType("Person", typeof(PersonEntity));
            engine.Execute(@"
            var myMethod = function(model) { 
                return model.Name;
            };");

            object response = engine.CallFunction("myMethod",
                // currently only types listed in
                // JavaScriptEngineSwitcher.Core.Helpers.ValidationHelpers._supportedTypes 
                // can be sent as parameters to CallFunction. 
                new PersonEntity()
                {
                    Name = new NameEntity()
                    {
                        FirstName = "John",
                        LastName = "Doe"
                    }
                });

            Assert.IsTrue(response.GetType() == typeof(NameEntity));
        }
    }
}

Currently it is possible for a HostObject to be returned as a result of CallFunction.

class Program
{
    static void Main(string[] args)
    {
        var settings = new ChakraCoreSettings()
        {
            DisableEval = false,
            DisableBackgroundWork = true,
            DisableNativeCodeGeneration = false,
            EnableExperimentalFeatures = false
        };

        JsEngineSwitcher.Instance.EngineFactories.AddChakraCore(settings);
        JsEngineSwitcher.Instance.DefaultEngineName = ChakraCoreJsEngine.EngineName;
        using (var engine = JsEngineSwitcher.Instance.CreateDefaultEngine())
        {
            engine.EmbedHostType("Person", typeof(PersonEntity));
            engine.Execute(@"
            var myMethod = function() { 
                var p = new Person();
                return p;
            };");

            object response = engine.CallFunction("myMethod");

            Assert.IsTrue(response.GetType() == typeof(PersonEntity));
        }
    }
}
Taritsyn commented 7 years ago

Hello, Matt!

It would be nice to be able to send host objects as arguments to CallFunction.

Unfortunately, it is impossible to implement in the JavaScript Engine Switcher from a technical point of view, because most JavaScript engines do not support this feature.

In principle, you can pass objects as strings by using JSON serialization. As an example, see LessCompiler class from the Bundle Transformer.

Taritsyn commented 7 years ago

In addition, you can try to use JsRT API directly - https://www.smashingmagazine.com/2016/09/building-hybrid-apps-with-chakracore/

tennaito commented 7 years ago

Hello, Matt!!

I would like that solution too! You could use just a wrapper function to do so, and use the javacript engine json parser.

---- test.js ----

var func = function (p) {
    return "Hello World " + p.Name + "!";
}

---- TestController.cs ----

// into some action method

JsEngineSwitcher engineSwitcher = JsEngineSwitcher.Instance;
engineSwitcher.EngineFactories.Add(new ChakraCoreJsEngineFactory());
engineSwitcher.DefaultEngineName = "ChakraCoreJsEngine";

// usage
IJsEngine engine = JsEngineSwitcher.Instance.CreateDefaultEngine();

// evaluate my file
engine.Evaluate<string>(System.IO.File.ReadAllText(HttpContext.Server.MapPath("~/Scripts/test.js")));

// create a wrapper function to the target one
engine.Evaluate<string>(@"var funcWrapper = function(str) {
    // target 'func' function
    return func(JSON.parse(str));
};");

// execute the wrapper function
var res = engine.CallFunction<string>("funcWrapper", new JavaScriptSerializer().Serialize(new Person { Name = "Joe"}));

// res will be: "Hello World Joe!"

In this case you could create an extension method to encapsulate the creation of a generic parsing javascript function.

Something like this:

    public static class JsEngineExtension
    {
        public static T CallFunction<T, O>(this IJsEngine engine, string name, O obj)
        {
            engine.Evaluate<string>(@"var wrapper = function(callback, data){
                // no window object here... there is some way to prevent eval usage?
                return eval(callback)(JSON.parse(data));
            };");
            return engine.CallFunction<T>("wrapper", name, new JavaScriptSerializer().Serialize(obj));
        }
    }

// execution now is like this: var res = engine.CallFunction<string, Person>("func", new Person { Name = "Joe"});

charlesprakash commented 6 years ago

@Taritsyn - in order to use the JsRT APIs, you should expose the underlying type so we can write custom type conversions. However, the underlying type is internal and is not exposed. So there is no way to cast the returned value or create a value of type JsValue (in the case of ChakraCore) to pass into the function as arguments.

Can you please provide a sample of how you think we could use JsRT APIs to convert the function arguments and return values to the underlying type using the JavaScriptEngineSwitcher library?

Taritsyn commented 5 years ago

Hello, Charles!

I know the ChakraCore engine supports this feature, but not all other JS engines support it. I want to remind for what purpose the JavaScript Engine Switcher library was created:

JavaScript Engine Switcher determines unified interface for access to the basic features of popular JavaScript engines (MSIE JavaScript Engine for .Net, Microsoft ClearScript.V8, Jurassic, Jint, ChakraCore and VroomJs). This library allows you to quickly and easily switch to using of another JavaScript engine.

coader commented 3 years ago

it's threadsafe if can pass object as parameter, if use EmbedHostType, the Precompiled script can't run in multithread

Taritsyn commented 3 years ago

@coader Give a specific example.