This project is read-only.

SpiderMonkey .Net embedding objects instead of class

Topics: Developer Forum
May 7, 2013 at 4:31 PM
Edited May 7, 2013 at 4:33 PM
Hi,

From reading the spidermonkey source code, i sort of understand the way Spidermonkey calls managed .Net code static method is:
  • Store the .Net reflection information in memory with its name and arguments.
  • Define a JSFunctionSpec that contains the name of the reflected method and a Native Callback method that match the JSNative signature.
  • When the function is called in the javascript code, the Native callback method tries to match the name with the .Net reflection information, and then does an invoke on the .net reflection.
This all works well for a static method, but when the method is part of an object, the .Net reflection Methodinfo.Invoke requires an object instance to be passed in as well, I can't see anyway how the native callback method would be able to reference the original object. Does anyone have a good solution for this?
May 7, 2013 at 4:42 PM
This is a fairly important change we'd need to make to SMNET. What I want to be able to do is pass instances to Spidermoney instead of just static classes.

In C#:
public class Account
{
    public int id { get; set; }
    public string name { get; set; }

    public Account(int id, string name) {
        this.id = id;
        this.name= name;
    }
    public void save() {
        // save to database
    }
}

runtime.Embed(typeof(Account));
runtime.Expose("master", new Account(0,"Global Default"));  // this is new
Then in Javascript:
master.name = "Default Account";
master.save();

var testing = new Account(1,"New Account");
testing.save();
May 7, 2013 at 8:08 PM
While bindings appear static, instance data is available in objects created in the runtime. The limitation is that you have no reference to objects new'd up in the runtime. But the managed world can be aware of object references created in the runtime, if you expose a managed class that acts as a factory to these objects.
May 7, 2013 at 9:22 PM
Clarification question. Does that mean that if we construct (instantiate) two instances of a class in C#, then pass both to SMNET that we no way of relating methods or properties back to the original C# instances. We'd need to create a factory class which is embedded in SMNET to instantiate classes.
static class Factory {
    public Account createAccount(int id, string name) {
        return new Account(id, name);
    }
}
runtime.Embed(typeof(Account));
runtime.Embed(typeof(Factory));
And in Javascript...
var master = Factory.createAccount(0,"Global");
master.name = "Default Account";
master.save();
Additional question:
What if we needed to have two instances in a collection...
class Group
{
    public Account[] accounts;
    public Group(params Account[] accounts) {
        this.accounts = accounts;
    }
}
runtime.Embed(typeof(Account));
runtime.Embed(typeof(Group));

runtime.Expose("group1", new Group(new Account(0,"Global Default"), new Account(1,"First"));  // this is new
And then in Javascript...
group1.accounts[0].name = "Default Account";
group1.accounts[0].save();
group1.accounts[1].name = "First Account";
group1.accounts[1].save();
Would that work?
May 7, 2013 at 9:37 PM
Yes that works on both counts. Think to normal JS programming as an example, you never see "new AnchorElement()" or anything like that. You have calls such as: "document.createElement("A");". This allows the native Firefox context to build the anchor in its own internal model of the HTML page. Remember you can expose "group1" and add Accounts to it anytime at runtime, not just at embedding time.
May 7, 2013 at 11:21 PM
Ok wait, how do I make my second example work?
What I'm trying to do is create an object instance in C# and then pass that fully constructed instance to SMNET. For our project, we will need to run scripts on multiple different instances (different customer accounts) simultaneously.
May 8, 2013 at 12:59 AM
I think you have it right. Accessing an object would be done through a container API, e.g. using the web analogy again: document.getElementById()
May 8, 2013 at 3:40 PM
The problem with our implementation using your analogy of document.getElementById() is that for us some parts of the document would be restricted based on the user running the script.

Embedding a static "document" class to with a "getElementById" method wouldn't work since the document would be different per customer.

How do I create a unique "document" for each customer and pass it in?
May 8, 2013 at 9:24 PM
It does always boil down to some sort of global API, but all these context related issues have solutions. If I'm right that you are hosting multiple engines, that map engines or JSContexts to your users. When the factory/object store is invoked it will have references to JSEngine & JSContext, and so you could utility that mapping to provide the JS-world with the correct 'view'.
Jun 15, 2013 at 7:39 PM
Edited Jun 15, 2013 at 7:52 PM
When you embed any type. Whether it be static or an instance class. That object becomes available to the JavaScript runtime. Static classes become properties of the Global object (as per spec) and instance classes may be created using the JavaScript 'new' keyword.

If you MUST pass an instance of an embedded type use: SMScript.CreateObject you can then use SMScript.GetInstancePointer which can be used in conjunction with SMScript.CallFunction (or spidermonkey API functions) to perform any functions that have not been converted to a .NET binding. This pointer is how the engine recognizes the new object, the request for an expose method requires the use of a property name, this would create a "static" object, and not an instance. Objects in JavaScript are ALL considered 'functions' and technically the difference between instance and static is whether or not the instance has been applied as a property of the global object and whether the context has access to the 'new' function.

Please keep this in mind when developing a JavaScript API. JavaScript's semantics differ greatly from C#'s and this binding was developed to make the transition much easier. It will not force JavaScript to work like C#.

Thank you :)