-
Notifications
You must be signed in to change notification settings - Fork 127
Exposing a .NET class to JavaScript
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.
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|Supported types].
- 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.
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