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

Exposing a .NET class to JavaScript

Building a property bag class

To start with, let's create a class that contains a couple of properties.

using Jurassic;
using Jurassic.Library;

public class AppInfo : ObjectInstance
{
    public AppInfo(ScriptEngine engine)
        : base(engine)
    {
        // Read-write property (name).
        this["name"] = "Test Application";

        // Read-only property (version).
        this.DefineProperty("version", new PropertyDescriptor(5, PropertyAttributes.Sealed), true);
    }
}

In this example there are a few things to note:
  • Classes that are exposed to JavaScript are required to inherit from Jurassic.Library.ObjectInstance.
  • Passing a ScriptEngine to the base class constructor means that the object will have no prototype. Therefore the usual functions (hasOwnProperty, toString) will not be available.
  • The class indexer can be used to create read-write properties.
  • DefineProperty can be used to create read-only properties.

Here's an example of how to create and use the new class:

var engine = new Jurassic.ScriptEngine();
engine.SetGlobalValue("appInfo", new AppInfo(engine));
Console.WriteLine(engine.Evaluate<string>("appInfo.name + ' ' + appInfo.version"));

This will output "Test Application 5" to the console.

Building a class with static functions

The next step up is to create a class with static functions, similar to how the built-in Math object works. For example, say you want to create a new Math2 object with a log10 function:

using Jurassic;
using Jurassic.Library;

public class Math2 : ObjectInstance
{
    public Math2(ScriptEngine engine)
        : base(engine)
    {
        this.PopulateFunctions();
    }

    [JSFunction(Name = "log10")]
    public static double Log10(double num)
    {
        return Math.Log10(num);
    }
}

Note the following:
  • PopulateFunctions searches the class for JSFunction attributes and creates a function for each one it finds.
  • The JSFunction attributes allows the JavaScript function name to be different from the .NET method name.
  • The parameter types and the return type must be on the list of supported types.
  • Please note: This feature requires the latest build from source control and is not yet in the official Downloads Decorating a property with the JSProperty attribute allows properties to be exposed to script as accessors. You can therefore code properties, complete with backers, in CLR, or make read-only properties in CLR code.

Here's an example of how to create and use the new class:

var engine = new Jurassic.ScriptEngine();
engine.SetGlobalValue("math2", new Math2(engine));
Console.WriteLine(engine.Evaluate<double>("math2.log10(1000)"));

This will output "3" to the console.

Building an instance class

Objects that can be instantiated, like the built-in Number, String, Array and RegExp objects, require two .NET classes, one for the constructor and one for the instance. For example, let's make a JavaScript object that works similar to the .NET Random class (with a seed, since JavaScript doesn't support this):

using Jurassic;
using Jurassic.Library;

public class RandomConstructor : ClrFunction
{
    public RandomConstructor(ScriptEngine engine)
        : base(engine.Function.InstancePrototype, "Random", new RandomInstance(engine.Object.InstancePrototype))
    {
    }

    [JSConstructorFunction]
    public RandomInstance Construct(int seed)
    {
        return new RandomInstance(this.InstancePrototype, seed);
    }
}

public class RandomInstance : ObjectInstance
{
    private Random random;

    public RandomInstance(ObjectInstance prototype)
        : base(prototype)
    {
        this.PopulateFunctions();
        this.random = new Random(0);
    }

    public RandomInstance(ObjectInstance prototype, int seed)
        : base(prototype)
    {
        this.random = new Random(seed);
    }

    [JSFunction(Name = "nextDouble")]
    public double NextDouble()
    {
        return this.random.NextDouble();
    }
}

Note the following:
  • You need two classes - one is the constructor (i.e. the function object that you call new on) and one is for the instance object.
  • The ClrFunction base class requires three parameters: the prototype for the function object, the name of the function and the prototype for any instances created using the function.
  • The JSConstructorFunction attribute marks the method that is called when the new operator is used. You can also use JSCallFunction to mark the method that is called when the function is called directly.
  • The RandomInstance class has two constructors - one is used to initialize the prototype, one is used to initialize all other instances. PopulateFunctions should only be called from the prototype object.

Here's an example of how to create and use the new class:

var engine = new Jurassic.ScriptEngine();
engine.SetGlobalValue("Random", new RandomConstructor(engine));
Console.WriteLine(engine.Evaluate<double>("var rand = new Random(1000); rand.nextDouble()"));

This will output "0.151557459100875" to the console (since we are using a seed, the first call to nextDouble() will be the same every time).

This example is much more involved, but it supports all of the advanced JavaScript concepts:
  • rand supports the built-in Object functions (hasOwnProperty, toString, etc).
  • rand utilizes prototypical inheritance. In this case you have the following prototype chain: random instance (with no properties) -> random prototype (with nextDouble defined) -> object prototype -> null.


Next tutorial: Loading a script from a custom source

Last edited Apr 7, 2012 at 7:02 AM by paulbartrum, version 9

Comments

No comments yet.