.NET Interoperability Overview
Relevant source files
The following files were used as context for generating this wiki page:
Jurassic is a JavaScript engine designed to be integrated into .NET programs, providing a powerful bridge between the JavaScript and .NET worlds. This interoperability allows JavaScript code to seamlessly access and manipulate .NET objects and methods, and for .NET code to execute and interact with JavaScript functions and variables.
The core of this system revolves around exposing .NET constructs to the JavaScript ScriptEngine. This can be done by setting global variables, exposing functions, or wrapping entire .NET classes as JavaScript objects. A key architectural feature is an attribute-based code generation system that automatically creates the necessary boilerplate and type conversion logic to facilitate smooth communication between the two runtimes.
Exposing .NET Code to JavaScript
There are several primary mechanisms for making .NET functionality available within the JavaScript environment.
Setting Global Values and Functions
The simplest way to share data and functionality is through the ScriptEngine instance.
SetGlobalValue(name, value): Exposes a .NET object as a global variable in the JavaScript context.SetGlobalFunction(name, delegate): Exposes a .NET delegate (e.g.,Func<>orAction<>) as a global JavaScript function.
// From: Unit Tests/Core/DocumentationSamples.cs
// Exposing a simple value
var engine = new ScriptEngine();
engine.SetGlobalValue("interop", 15);
engine.ExecuteFile(@"../../../Core/Sample Files/globals1.js");
Assert.AreEqual(20, engine.GetGlobalValue<int>("interop"));
// Exposing a .NET function
engine.SetGlobalFunction("test", new Func<int, int, int>((a, b) => a + b));
Assert.AreEqual(11, engine.Evaluate<int>("test(5, 6)"));
Sources: Unit Tests/Core/DocumentationSamples.cs:28-34, Unit Tests/Core/DocumentationSamples.cs:42-45
Exposing Custom .NET Classes
For more complex interactions, entire .NET classes can be exposed as JavaScript objects. This is typically achieved by creating a C# class that inherits from Jurassic.Library.ObjectInstance.
Key components for this pattern include:
ObjectInstance: A base class for all objects in JavaScript. Custom .NET classes inherit from it to be represented as JS objects.[JSFunction]Attribute: Marks a C# method to be exposed as a function on the JavaScript object.[JSConstructorFunction]Attribute: Marks a method within aClrFunction-derived class to act as a JavaScript constructor.PopulateFunctions(): A method onObjectInstancethat, when called, automatically finds methods marked with[JSFunction]and exposes them.
The following diagram illustrates the class relationships for exposing custom .NET types as seen in the documentation samples.
This diagram shows how custom .NET classes like AppInfo, Math2, and RandomInstance inherit from ObjectInstance to become valid JavaScript objects, while RandomConstructor inherits from ClrFunction to act as a constructor.
Sources: Unit Tests/Core/DocumentationSamples.cs:55-121
Example: Exposing an Instance with Properties
The AppInfo class demonstrates exposing read-only properties by using DefineProperty in the constructor.
// From: Unit Tests/Core/DocumentationSamples.cs
public class AppInfo : ObjectInstance
{
public AppInfo(ScriptEngine engine)
: base(engine)
{
this.DefineProperty("name", new PropertyDescriptor("Test Application", PropertyAttributes.Sealed), true);
this.DefineProperty("version", new PropertyDescriptor(5, PropertyAttributes.Sealed), true);
}
}
// Usage
var engine = new ScriptEngine();
engine.SetGlobalValue("appInfo", new AppInfo(engine));
Assert.AreEqual("Test Application 5", engine.Evaluate<string>("appInfo.name + ' ' + appInfo.version"));
Sources: Unit Tests/Core/DocumentationSamples.cs:55-69
Example: Exposing a Class with Methods
The Math2 class uses the [JSFunction] attribute to expose a static .NET method as a JavaScript function on the object instance.
// From: Unit Tests/Core/DocumentationSamples.cs
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);
}
}
// Usage
var engine = new ScriptEngine();
engine.SetGlobalValue("math2", new Math2(engine));
Assert.AreEqual(3.0, engine.Evaluate<double>("math2.log10(1000)"));
Sources: Unit Tests/Core/DocumentationSamples.cs:71-89
Attribute-Based Stub Generation
A significant part of Jurassic's .NET interoperability is its automated code generation process. This system uses attributes to generate "stub" methods that bridge the gap between the JavaScript engine's calling convention and the target .NET methods.
Key Attributes
| Attribute | Description |
|---|---|
[JSInternalFunction] | Exposes a C# method as a standard JavaScript function. Used extensively for built-in objects like GlobalObject. |
[JSConstructorFunction] | Designates a method as the implementation for a JavaScript constructor (new MyObject()). |
[JSProperty] | Exposes a C# property with a getter and optional setter. |
[JSField] | Exposes a C# field. |
Sources: Attribute Code Generation/Program.cs:113-140
Code Generation Workflow
The Attribute Code Generation tool parses C# source files, finds these attributes, and generates corresponding .g.cs files. These generated files contain private static stub methods (e.g., __STUB__MethodName, __GETTER__PropertyName) that are wrapped in ClrStubFunction instances.
This flowchart shows how a C# method decorated with an attribute is processed by a tool to generate a stub method, which then calls the original C# code.
Sources: Attribute Code Generation/Program.cs, Jurassic/Library/GlobalObject.cs, Jurassic/Library/GlobalObject.g.cs
The generated stub is responsible for:
- Checking the number of arguments passed from JavaScript.
- Providing default values for missing arguments.
- Using
TypeConverterto convert JavaScript types to the expected .NET types. - Calling the actual .NET method.
- Returning the result, ensuring it's a valid JavaScript type (or
Undefined.Valueforvoidmethods).
Here is an example of a generated stub for the global decodeURI function:
// From: Jurassic/Library/GlobalObject.g.cs
private static object __STUB__DecodeURI(ScriptEngine engine, object thisObj, object[] args)
{
switch (args.Length)
{
case 0:
return DecodeURI(engine, "undefined");
default:
return DecodeURI(engine, TypeConverter.ToString(args[0]));
}
}
Sources: Jurassic/Library/GlobalObject.g.cs:25-34
Calling .NET from JavaScript: A Sequence Diagram
When JavaScript code invokes a function that is implemented in .NET, the Jurassic engine orchestrates a series of steps involving the generated stubs.
This diagram illustrates the sequence of events when a JavaScript call is made to a .NET-backed function.
Sources: Jurassic/Library/GlobalObject.g.cs:25-34, Jurassic/Library/GlobalObject.cs:60-70
Type Conversion
The TypeConverter class is essential for interoperability, handling the translation between JavaScript's dynamic types and .NET's static types. The stub generation logic relies heavily on this class.
Common conversions include:
TypeConverter.ToString(arg)TypeConverter.ToNumber(arg)TypeConverter.ToBoolean(arg)TypeConverter.ToInteger(arg)TypeConverter.ToObject(engine, arg)TypeConverter.ToObject<T>(engine, arg)
This logic is automatically inserted during the stub generation process.
Sources: Attribute Code Generation/Program.cs:516-556, Jurassic/Library/GlobalObject.g.cs
Summary
Jurassic's .NET interoperability is a cornerstone of its design, enabling developers to extend the JavaScript environment with the full power of the .NET framework. By exposing functions and objects through the ScriptEngine and leveraging a sophisticated attribute-based code generation system, Jurassic provides a seamless and efficient bridge between the two ecosystems. This allows for rich applications where JavaScript can be used for scripting, configuration, or dynamic logic while relying on robust, high-performance .NET code for heavy lifting.