microsoft/ClearScript

Properties of exposed host objects have undefined value

cronoxyd opened this issue · 4 comments

I'm currently struggling with the issue that all properties of exposed host objects return [undefined] when my script tries to access them and I can't figure out why. Here's my code:

internal class SampleClass
{
    public string Name { get; set; } = string.Empty;
    public byte SampleData { get; set; } = 0;
}

var eng = new V8ScriptEngine();
var sampleInst = new SampleClass()
{
    SampleData = "abc"
};
eng.AddHostObject("inst", sampleInst);
// Throws "TypeError: Cannot read properties of undefined (reading 'Contains')\n    at Script [temp]:1:11 -> inst.Name.Contains(\"b\")"
eng.Evaluate("inst.Name.Contains(\"b\")");

I've also tried exposing the object using the Script property of the engine but to no avail. Thanks in advance!

Hello @cronoxyd,

Thanks for posting your sample code. Our observations:

  1. SampleData = "abc" doesn't compile, because SampleData is of type byte rather than string.
  2. The properties of inst aren't visible in script code, because SampleClass is internal.
  3. System.String.Contains isn't available in script code, because .NET strings are converted to JavaScript strings.

If SampleClass were public, the following would work:

var sampleInst = new SampleClass { Name = "abc" };
eng.AddHostObject("inst", sampleInst);
Console.WriteLine(eng.Evaluate("inst.Name.includes('b')"));

Alternatively, if SampleClass must remain internal, you could do this:

var sampleInst = new SampleClass { Name = "abc" };
eng.AddHostObject("inst", HostItemFlags.PrivateAccess, sampleInst);
Console.WriteLine(eng.Evaluate("inst.Name.includes('b')"));

Good luck!

Thanks for the quick reply! First of all apologies about the wrong assignment, I condensed that code from a larger testing suite and I am actually assigning to Name, not SampleData. And with your suggestions, my code works fine now. I was operating under the assumption that exposed objects and their members are strictly kept on the C# side in terms of the API.

Out of curiosity: If I rather wanted to use the .NET string implementation (or at least its members) than the Javascript one, could I force ClearScript to keep the .NET implementation?

Hi @cronoxyd,

If I rather wanted to use the .NET string implementation (or at least its members) than the Javascript one, could I force ClearScript to keep the .NET implementation?

There's no way to suppress string conversion globally. Using ClearScript would be very painful (if not impossible) without automatic conversion of fundamental data types.

However, you can use host variables to selectively expose .NET's string implementation. Here's an example that adds several .NET members to JavaScript's string prototype:

dynamic setup = eng.Evaluate(@"(function (host) {
    const toClr = str => host.newVar(str.valueOf());
    const clrMembers = [ 'Length', 'Contains', 'Insert', 'Remove' ];
    for (let name of clrMembers) {
        Object.defineProperty(String.prototype, name, {
            get: function() { return toClr(this)[name]; }
        });
    }
})");
setup(new HostFunctions());

With this setup in place, you can do things like this:

eng.AddHostType(typeof(Console));
eng.Execute(@"
    const str = 'a story';
    Console.WriteLine(str.Contains('to'));
    Console.WriteLine(str.Insert(2, 'likely '));
");

Note that this isn't very efficient, as each invocation of a .NET string member involves a string copy and several hops across the boundary to the host.

Cheers!

Okay definitely looks like a only-when-you-have to thing. Still good to know just in case. Thank you so much for taking the time to write the examples!

Kind regards,

cronoxyd