This project has moved. For the latest updates, please go here.

Weird Parameter Passing Behavior

Jul 30, 2014 at 4:25 AM
Edited Jul 30, 2014 at 8:11 AM
Hello!

I am just getting started with ClearScript but I am finding some weird behavior when it comes to passing .Net objects (strings, integers) into scripts and then using them to return new values.

To be more clear when I load the engine my script is executed and it runs a few functions and fills up an array I am calling Chunks[].

Later on, I want to access the Chunks[] array in a few function, here is a slimmed down, modified version of it.
function getChunk(num) {
    return Chunk[num];
}
That section of code returns fine if I call it like this:
 j = engine.Evaluate("getChunk(" & chunkNum & ")")
Please note that chunkNum is a long value which is equal to 1 in my tests.

If I call it like this, however, it returns as undefined.
j = engine.Script.getChunk(chunkNum)
To conclude, the above code works if I alter the javascript function to assign num to another value (n for example) and then return the chunk based on the index of n.
function getChunk(num) {
        n = num;
    return Chunk[n];
}
This makes absolutely no sense to me. It "works" but some explanation of how the different ways to pass parameters work would be awesome.



(Edit) Question 2:
I am also confused about another set of behavior that I am seeing. Keep in mind that I am completely new to dynamic objects.

Anyways, if I assign a value to d, say an array of some type and in my engine.Evaluate code I return d. If I try watch the value the function returns, it is correct. But when I take that and try to assign it to a local object or variable it crashes with a crossed a native/managed boundary seshexception. But if I assign my local variable to engine.Scripts.d it works perfectly fine. It is obvious I am missing something simple or just not understanding basic concepts, mind helping me out?


Thanks for all of your work in the library, so far it is great. I also appreciate you taking the time to read this and decode my 3am discussion threads.
-JC
Coordinator
Jul 30, 2014 at 2:29 PM
Edited Jul 30, 2014 at 2:39 PM
Hi JC,

We've replicated the first issue, and it appears to be a bug in ClearScript. To be honest, we haven't done much testing with VB.NET hosts. In most cases the host language is irrelevant, but dynamic binding through the Script property involves quite a bit of language-specific machinery. We're investigating.

As for your second question, can you provide a code sample that demonstrates the issue?

Thanks!
Jul 30, 2014 at 2:56 PM
Thanks so much for looking into this.

Here is a short code sample to elaborate on my second question.
Microsoft.ClearScript.V8.V8ScriptEngine se = new Microsoft.ClearScript.V8.V8ScriptEngine();
            //This line fails.
            var j = se.Evaluate("var d = 'this is d'; return d;");
            
            //Along with these.
            //se.Execute("var d = 'this is d';");
            //var j2 = se.Evaluate("return d;");

            //But these two work....
            //se.Execute("var d = 'this is d';");
            //var j1 = se.Script.d;
I understand the complications that VB introduces, especially with dynamic objects. Translating my project to C# is always in option - this library is worth the work - it would take a few days, though, so it is something I would like to refrain from if possible. Please let me know how it all works out, if there are any other code samples I can provide, or anything I can do to help!

-JC
Coordinator
Jul 30, 2014 at 4:53 PM
Hi again,

It looks like the failures in the code samples above are caused by the use of JavaScript return statements outside of a function:
engine.Evaluate("return d;"); // error
engine.Evaluate("d"); // OK
Also, JavaScript statements such as var don't have values, but expressions do, so if you'd like your script to return a value, make sure it ends with an expression:
engine.Evaluate("var d = 123"); // returns no value (undefined)
engine.Evaluate("var d = 123; d"); // returns 123
By the way, we'll post a fix for the VB.NET issue very soon.

Cheers!
Coordinator
Aug 1, 2014 at 8:19 PM
Hi JC,

The fix for the VB.NET issue is now available here.

Thanks again!
Aug 3, 2014 at 11:32 PM
Codeplex failed to send me the update email. Thanks so much for the update!!!!

-JC
Aug 4, 2014 at 5:24 AM
Edited Aug 4, 2014 at 5:43 AM
Ok, last question two questions, Mr. ClearScript.

Here is my current scenario,
  1. I run a script which returns a string and I assign it to an object.
  2. The object returns the V8ScriptItem.
  3. Whenever I try to reference the object, for its value, it tries to invoke it as a script (or so it seems).
Basic line of code I am using is below...
c is an object but the function is returning a string.
s1 is a string, CType is VBs way of casing, (String)
c = JSEngine.Script.getChunkJSON(chunkNum)
s1 = CType(c, String)
The second line errors. Screenshot of the error and call stack can be found below.
http://puu.sh/aDTlS/291299d450.png
http://puu.sh/aDTir/407d48e142.png

Any advice, or another way I should reference the result so it is seen as a value instead of a script to execute?


Second question, should be a lot simpler... Could you show me show a cross between the Script.Method(args) and Engine.Evaluate("Code") would look like? My hope is to have a function that accepts a method name as a parameter and arguments as a second one like NeoLua used to.

Engine.Evaluate(MethodName,Args[]);

Thanks so much for your help and your continued support. BTW, the first fix works great.
-JC
Coordinator
Aug 4, 2014 at 4:00 PM
Edited Aug 4, 2014 at 4:56 PM
Hi JC,

ClearScript converts strings automatically, so if the script function returns a string, the host should see it as a .NET string, and casting via CStr() or CType() should work. It appears instead that your return value is a script object. Can you check (or share) the getChunkJSON() source code?

Regarding, your second question, please see here. Edit: In VB.NET, the extension class might look something like this:
Imports System.Dynamic
Imports Microsoft.ClearScript
Module ScriptEngineExtensions
    <System.Runtime.CompilerServices.Extension>
    Public Function InvokeFunction(engine As ScriptEngine, name As String, args() As Object)
        Dim host As New HostFunctions
        CType(host, IScriptableObject).OnExposedToScriptCode(engine)
        Dim func = host.getProperty(CType(engine.Script, IDynamicMetaObjectProvider), name)
        Dim del = CType(host.func(Of Object)(args.Length, func), System.Delegate)
        InvokeFunction = del.DynamicInvoke(args)
    End Function
End Module
Cheers!
Aug 4, 2014 at 5:58 PM
Hello again!

Here is what my getChunkJSON function looks like...
function getChunkJSON(num) {
    return JSON.stringify(Chunks[num]);
}
That code simply returns a string value. That is not my exact code, but that has the same result. If I try to change my VB code to something a little simpler like,
s1 = LUA.JSEngine.Script.getChunkJSON(chunkNum)

Where s1 is defined as a string, I get a "Conversion from type 'V8ScriptItem' to type 'String' is not valid." cast exception error.


As for the second question, that block of code looks like it will work great, thanks so much!

-JC
Coordinator
Aug 4, 2014 at 7:57 PM
Hi JC,

That code simply returns a string value. That is not my exact code, but that has the same result.

Hmm, is it possible that getChunkJSON() actually returns a string object rather than string - that is, does it do something like this:
function getChunkJSON(num) {
    return new String(JSON.stringify(Chunks[num]));
}
Thanks!
Aug 4, 2014 at 9:33 PM
Yeah, that is more than likely because the final step is a string compressor with a ton of String class functions. Is there an easy way to read the string object as a string without using the .toString() function in JS itself?
Coordinator
Aug 5, 2014 at 2:58 PM
Hi JC,

Is there an easy way to read the string object as a string without using the .toString() function in JS itself?

Sure, you can call toString() or valueOf() from the host:
s = engine.getChunkJSON(num).valueOf()
Of course, this will break if getChunkJSON() changes and starts returning normal strings.

In general we don't recommend explicit usage of JavaScript's String, Number, or Boolean objects, as their behavior can be unexpected:
if (new String('foo') == new String('foo')) {
    // code here WILL NOT be executed
}
if (new Boolean(false)) {
    // code here WILL be executed
}
Good luck!
Aug 5, 2014 at 5:12 PM
Alright thanks, and hopefully my last question... I am using the Function you provided above for calling methods by name and arguments..

Every time it tries to actually invoke the delegate it errors on the function below.
public TResult InvokeTarget()
            {
                return Invoke(() => (TResult)target());
            }
With a cannot invoke a non-delegate type error.

I know my function exists and is returning a ScriptItem but I have no idea what is going on in the interim.

THanks again for all of your help,
-JC
Coordinator
Aug 5, 2014 at 8:20 PM
Hi JC,

It sounds like the named property you're invoking isn't a function :)

The following test program demonstrates the extension module above:
Module TestModule
    Sub Main()
        Using engine As New V8ScriptEngine
            engine.AddHostType("Console", GetType(System.Console))
            engine.Execute("function foo(arg) { Console.WriteLine('Foo: {0}', arg); }")
            engine.InvokeFunction("foo", {123.456})
        End Using
    End Sub
End Module
Is there anything you're doing differently?

Thanks!
Aug 5, 2014 at 9:11 PM
No. Here is my exact code with your test above. (Little messier for testing purposes.)
Public Function InvokeJSFunction(name As String, args() As Object)
        Dim host As New HostFunctions
        JSEngine.AddHostType("Console", GetType(System.Console))
        JSEngine.Execute("function foo(arg) { Console.WriteLine('Foo: {0}', arg); }")
        name = "foo"
        args = {123.456}
        CType(host, IScriptableObject).OnExposedToScriptCode(JSEngine)
        Dim func = host.getProperty(CType(JSEngine.Script, IDynamicMetaObjectProvider), name)
        Dim del = CType(host.func(Of Object)(args.Length, func), System.Delegate)
        InvokeJSFunction = del.DynamicInvoke(args)
    End Function
It fails at the same part, granted I am sending arguments this time.
public TResult InvokeTarget(T1 a1)
            {
                return Invoke(() => (TResult)target(a1));
            }
Any ideas?

-JC
Coordinator
Aug 5, 2014 at 9:38 PM
Are the exceptions you're seeing unhandled? The C# dynamic infrastructure throws and handles exceptions internally. Please test outside the debugger, or just continue when you hit first-chance exceptions of type RuntimeBinderException.