Array as return type

Jun 13, 2013 at 11:19 PM
Ive been trying to return an array (or basicly any collection) with strings from C# to V8 and it doesnt work. Is there any way to return a collection to V8 so that the return type becomes a javascript string array?

My current workaround is to JSON it in c# before I return it and parse the json before using it in javascript. It works, but feels really clumsy. If this is the best way, cant clearscript do it for me? At least for base types.
Coordinator
Jun 14, 2013 at 2:39 AM
Edited Aug 3, 2013 at 3:21 AM
Hi Klator, and thanks for your question!

There are several ways to do what you need, and JSON serialization should not be necessary. One thing you could do is access a .NET array directly from JavaScript:
public class Foo {
    public string[] GetNames() {
        return new[] { "Adolph", "Blaine", "Charles", "David" };
    }
}
and then:
engine.AddHostObject("foo", new Foo());
engine.AddHostType("Console", typeof(Console));
engine.Execute(@"
    names = foo.GetNames();
    for (var i = 0; i < names.Length; i++) {
        Console.WriteLine(names[i]);
    }
");
On the other hand, if it's important that the method's return value be a JavaScript array, ClearScript lets you do that too:
public class Foo {
    private readonly ScriptEngine engine;
    public Foo(ScriptEngine engine) {
        this.engine = engine;
    }
    public object GetNames() {
        dynamic jsArray = engine.Evaluate("[]");
        jsArray.push("Adolph");
        jsArray.push("Blaine");
        jsArray.push("Charles");
        jsArray.push("David");
        return jsArray;
    }
}
and then:
engine.AddHostObject("foo", new Foo(engine));
engine.AddHostType("Console", typeof(Console));
engine.Execute(@"
    names = foo.GetNames();
    for (var i in names) {
        Console.WriteLine(names[i]);
    }
");
Please let us know if these suggestions don't work for you! Good luck!
Jun 14, 2013 at 11:20 AM
Yeah, option 1 is exactly what Ive done. (Or so I think.)

This is what I do:
    public class ElementCollection : List<Element>
    {
        [ScriptMember(Name = "text")]
        public string[] Text()
        {
            // Desired output.
            var s = this.Where(e => e.Text() != "").Select(e => e.Text()).ToArray();

            // Test to compare types
            var a = new[] { "Adolph", "Blaine", "Charles", "David" };

            return s;
        }
   }
In javascript I then populate a part of an object with this.

I actually first encountered this problem when I tried to JSON.stringify the populated object. (Circular reference error)

Anyway, in JS I do:
function parseArticles(page) {
    var c = page.element.findAll("div.article_content").text();

    var a = { "header": h, "content": c };

    // Type test
    var myArray = ["1", "2", "3"];

    // myArray.getName() returns Array
    $eng.debug("TYPENAME Array:" + myArray.getName()); 
    
    // c.getName() returns HostObject
    $eng.debug("TYPENAME:"+c.getName());

    $eng.out(a);
}
$eng.out() then tries to make JSON out of it and it crashes. But thats beside the point, myArray and a (in the second example) has different types, which is my problem. (I think)

I never explicitly expose the ElementCollection class to the engine, its just returned from a different host object.

The getName() function is ripped from Stack Overflow, its this:
Object.prototype.getName = function () {
    var funcNameRegex = /function (.{1,})\(/;
    var results = (funcNameRegex).exec((this).constructor.toString());
    return (results && results.length > 1) ? results[1] : "";
};
Coordinator
Jun 14, 2013 at 2:28 PM
Edited Aug 3, 2013 at 3:21 AM
Hi Klator,

It looks like JSON.stringify() can't work with .NET arrays. Each .NET array has a property named SyncRoot that simply returns the array instance. This makes the array a "circular" object and therefore incompatible with JSON.

In general, .NET objects often have lots of methods and other non-data members that make them JSON-unfriendly. Therefore we recommend in this case that you use script arrays instead. You can use something like this to make it dead simple:
public static class ScriptHelpers {
    public static object ToScriptArray<T>(this IEnumerable<T> source, ScriptEngine engine) {
        dynamic array = engine.Evaluate("[]");
        foreach (var element in source) {
            array.push(element);
        }
        return array;
    }
}
and then, in your .NET method:
var s = this.Where(e => e.Text() != "").Select(e => e.Text()).ToScriptArray(engine);
You could also make ToScriptArray() more sophisticated - e.g., it could handle arrays within arrays, it could convert elements to script objects, etc.

Cheers!
Jun 14, 2013 at 2:33 PM
Aha, I see.

I thought Clearscript would handle the conversion from .NET array to javascript array.
Yeah, that will work. And, yeah, its dead simple. Thanks.

Then again, your solution is dependent on me having access to the engine object at all times.
Guess Ill have to do some refactoring.

Ill try it right away.

K.
Coordinator
Jun 14, 2013 at 3:03 PM
Edited Aug 3, 2013 at 3:22 AM
Yes, for better and worse, ClearScript is all about seamless access rather than conversion :)

The idea is that with seamless access, conversion (should you need it) is easy and can be tailored to your requirements. We agree however that the need to pass the engine around might be painful.

What if ClearScript allowed something like this:
[ScriptMember(Name = "text", Flags = ScriptMemberFlags.ConvertArray)]
public string[] Text()
{
    // ...
}
The conversion from (one-dimensional) .NET arrays to script arrays is unambiguous and probably useful in many scenarios. Hmm... :)
Jun 14, 2013 at 3:10 PM
Ok. Makes sense.

Yeah, that would do the trick. Looks great.

Passing engine around is quite painful, then again I have some other objects (logging, statistics) that I also need to pass around, so its probably better if I just accept it. :) I would be great if you could make local-global (global static for a certain scope) variables in .NET. But thats not your problem.

Anyway, I like Clearscript, this is the second prototype im using it for, and it works really good.
Jun 17, 2013 at 1:23 PM
Ok, what about storing the engine in Thread Local Storage?
Im really not a big fan of lots of bolierplate code to pass an object through several objects just because I need it once somewhere.
Coordinator
Jun 17, 2013 at 1:54 PM
Thread Local Storage is certainly an option, although it could get hairy if you've got multiple engines and multiple threads.

Another possibility might be for your class to implement Microsoft.ClearScript.IScriptableObject - a single method that's called when your object is exposed to script code. The engine reference is passed into this method; you can store it in a private field and use it when necessary. Unfortunately this only makes sense if your object is only ever exposed in one engine.

Yet another option might be to do the conversion on the script side, but that also may not always be practical.

We're looking into the automatic array conversion feature. Stay tuned!
Jun 17, 2013 at 3:14 PM
How come it might get hairy? Isnt it a local storage per thread?
Anyway, I need some more stuff, besides the engine, like logging, that is engine specific.

I guesss I could keep em in a manager with a dictionary and return them when I pass in the engine. That way I could implement IScriptableObject and use engine from there.

Then again I could do that with threadId and that way get all the references I need. But it kinnda feels like crappy code, I need to be good at cleaning up after me to make it work in the long run.

K.
Coordinator
Jun 17, 2013 at 4:05 PM
Edited Aug 3, 2013 at 3:23 AM
How come it might get hairy? Isnt it a local storage per thread?

Yes, TLS is per-thread by definition, but there's no 1:1 mapping between V8 instances and threads. For example, you could run a script that initiates an asynchronous request that invokes a script-based completion callback on a random pool thread. In this scenario the callback would be invoked without the host having a chance to set up TLS. Or you might have ambiguity if multiple V8 instances are being used on the same thread. Without knowing your specific scenario, it's difficult to know how practical the TLS approach will be.

I guesss I could keep em in a manager with a dictionary and return them when I pass in the engine. That way I could implement IScriptableObject and use engine from there.

Sure, or you could bypass the dictionary and simply store the additional objects in the engine, although that might be a bit hacky :)

Then again I could do that with threadId and that way get all the references I need. But it kinnda feels like crappy code [...]

Yes, a dictionary keyed by thread ID is just TLS without much of the automatic housekeeping. It's not recommended.
Jun 17, 2013 at 4:18 PM
Edited Jun 17, 2013 at 4:22 PM
I see. Im glad we agree on the bad parts. :)

Im using clearscript to enable users to configure how a certain part of the system should behave.
Basicly, im giving them an interface (in js) to be able to call certain functions
In the long run we want external users to be able to use the interface.

getData from resources (webpages, documents, media).
Modify them in js.
setData for output.

Weve used similar solutions before to configure how pdf:s should be created.

Anyway, I need a couple of objects (loghandler, statistics, engine) to be accessible anywhere within this engines execution.


Another idea. What about a ConditionalWeakTable?
Would that work?
Coordinator
Jun 17, 2013 at 6:07 PM
It looks like you're dealing with a common design problem.

You have code that executes within a logical context that includes several properties. These properties must be accessible at any level of a potentially deep call stack. You could plumb them through as method arguments, or you could somehow make them accessible "out-of-band". The latter is more convenient, but how do you implement it?

If your scenario is simple enough, storing your context in TLS - or even as static data - might work just fine. Both are used very often for this purpose. You should just be aware that, like static data, TLS is not a magic bullet and can be problematic in some cases. Examples include scenarios involving interthread communications, RPC, asynchronous operations, fibers, etc.

ConditionalWeakTable is a great way to associate random data with an object without affecting GC, but in your case it seems like the key is not an object but some kind of implicit execution context. In many cases the thread (and therefore TLS) is perfectly sufficient. However, there are more complicated scenarios where TLS is not the answer.

Good luck!
Jun 18, 2013 at 8:28 AM
Yeah, I know.

I guess im going of topic here.
Thanks for tips and the discussion.

Im considering this issue resolved on my part.

Keep up the good work!