microsoft / Chakra-Samples

Repository for Chakra JavaScript engine related samples.
MIT License
216 stars 84 forks source link

Run a JS function from existing context? #60

Open adrientetar opened 7 years ago

adrientetar commented 7 years ago

(C#/UWP)

I would like to:

  1. Run a JS script that exports a function
  2. Run that function later on, on-demand

For 1. I just use ChakraCore.runScript(..). For 2. I wrote this ChakraCore.callFunction(..):

        public string callFunction(string function, string[] arguments)
        {
            IntPtr returnValue;

            try
            {
                // Get function id
                JavaScriptValue global;
                Native.JsGetGlobalObject(out global);
                JavaScriptPropertyId id;
                if (Native.JsGetPropertyIdFromName(function, out id) != JavaScriptErrorCode.NoError)
                    return "couldn't get property id.";
                // Is this the same as global.GetProperty(id)?
                JavaScriptValue jsFunc;
                Native.JsGetProperty(global, id, out jsFunc);

                // convert args
                JavaScriptValue[] jsArguments = new JavaScriptValue[arguments.Length+1];
                jsArguments[0] = global;
                int i = 1;
                foreach (string arg in arguments) {
                    JavaScriptValue jsArg;
                    UIntPtr length = new UIntPtr((uint) arg.Length);
                    Native.JsPointerToString(arg, length, out jsArg);
                    jsArguments[i] = jsArg;
                    i += 1;
                }

                //
                JavaScriptValue result;
                JavaScriptErrorCode err = Native.JsCallFunction(
                    jsFunc, jsArguments, (ushort) jsArguments.Length, out result);
                if (err != JavaScriptErrorCode.NoError)
                    return "couldn't execute function: " + err.ToString() + ".";

                // Convert the return value.
                JavaScriptValue stringResult;
                UIntPtr stringLength;
                if (Native.JsConvertValueToString(result, out stringResult) != JavaScriptErrorCode.NoError)
                    return "failed to convert value to string.";
                if (Native.JsStringToPointer(stringResult, out returnValue, out stringLength) != JavaScriptErrorCode.NoError)
                    return "failed to convert return value.";
            }
            catch (Exception e)
            {
                return "chakrahost: fatal error: internal error: " + e.Message;
            }

            return Marshal.PtrToStringUni(returnValue);
        }

This fails with "couldn't execute function: InvalidArgument". Please advise, JsRT documentation is (afaict) lackluster.

liminzhu commented 7 years ago

Thanks for posting the issue @adrientetar . The snippet you had there looks fine and doesn't give me any problem when I was trying something like,

runScript("function a(x) {return x}");
callFunction("a", ["HelloWorld"]);

Can you send me a small repo so that I can look at what exactly went wrong? Also, if your script exposes a single function, you can parse the script into a JsValueRef handle using JsParseScript and then pass the returned handle to JsCallFunction.

JohnMasen commented 7 years ago

The first element in "jsArguments" should be the object who holds the function . which is "global" in your code. It took me 2 days to figure this out.

liminzhu commented 7 years ago

Yep @JohnMasen the first element in arguments for JsCallFunction (which resembles Function.Prototype.call) is thisArg, which is documented here. Sorry it took you two days :/. Any suggestion to make it more discoverable?

JohnMasen commented 7 years ago

Suggestions: arguments: The arguments to the call (Requires thisArg as first argument of arguments.)

Remarks: The first parameter of arguments should be the object which holds the function. Use the result of JsGetGlobalObject(or JavaScriptValue.GlobalObject in c# wrapper) if this function is a global function.

JohnMasen commented 7 years ago

use Undefined works same as GlobalObject, however it may confuse the user that the first parameter should be a place holder.

liminzhu commented 7 years ago

Thanks @JohnMasen . I add a note in the Parameters section.