This project is read-only.
This documentation outlines some features only available to 1.5 Beta and up

Documentation is explained mainly with provided code samples (in C#). For a clearer usage example please download the smnet-demo solution which can be found in the SVN repository (warning, can look odd at times when testing new or buggy features lol).

Creating a runtime:

using smnetjs;

public class HelloSpidermonkey
{
    public static void Main(string[] args)
    {
        //Default runtime with default settings
        SMRuntime runtime = new SMRuntime();

        //Runtime using RuntimeOptions flags
        SMRuntime runtime = new SMRuntime(RuntimeOptions.DisableAutoEmbedding);
    }
}

RuntimeOptions flags are bitwise flags used by the runtime:
  • None
    • No optional settings applied
  • DisableAutoEmbedding
    • The engine will not automatically embed unknown types it comes across during reflection
    • This affects field types, property types, method return types, method parameter types and event handler types
    • If enabled, the above mentioned types should be embedded manually in order for them to function properly
    • Enabling this feature can reduce total memory consumption
  • EnableCompartmentalGC
    • With compartmental GC turned on, calling SMScript.GarbageCollect will invoke garbage collection only within the script's compartment, rather than the entire runtime
    • This can be a performance improvement in some situations
  • ForceGCOnContextDestroy
    • Specifies that the engine should force a full GC when a script is disposed
    • When scripts are created and destroyed often, this can lead to a performance degradation

Embedding types:

Many types from within the .net framework itself can be embedded and used successfully from javascript. The way types are embedded is mainly declarative, with the exception of attribute usage (explained later). That is to say, a type declared as static will be embedded as a static type, i.e. a property on the global object and instance types will be embedded with a class constructor and can be instanciated with the javascript "new" keyword.

Note: when embedding .net framework types. They function best with AutoEmbedding enabled.

//embedding the .net 'Environment' class
runtime.Embed(typeof(Environment));

//embedding the .net 'DateTime' class
runtime.Embed(typeof(DateTime));

Embedding custom types:

Embedding a custom type can be as simple as:

static class MyStaticJSClass
{
    public static int DoSomething() 
    {
        return 20;
    }
}

class MyInstanceJSClass
{
    public int DoSomething()
    {
        return 20;
    }
}

runtime.Embed(typeof(MyStaticJSClass));
runtime.Embed(typeof(MyInstanceJSClass));
Notes:
only public members are embedded, remember embedding rules are primarily declaritive. If a member must remain public but you wish to hide it from a script use the SMIgnore attribute (see below).

embedding supports overloaded methods, providing they have the same script-visible name. When using method attributes, to get predictable results from the attributes, only one attribute should be used for every method of the same name.

Example:
[SMMethod(Enumerate = false)] // <-- this applies to all overloads named 'print'
public void print(string text) { }

public void print(string format, params object[] args) { }

[SMMethod(Name = "print")] // <-- this will inherit the attribute property used above because it's name is now 'print'
public void Printing(string format, bool somearg, params object[] args) { }

Using attributes to control embedding:

Attributes that can be used to control how exactly a type is embedded are:
  • SMIgnore - can be used on any member to specifiy the member should not be embedded
  • SMEmbedded
    • Attribute used at class scope to control type specific embedding options
    • Named parameters are:
      • Name - the script-visible typename of the type (if required to be different from the declaration)
      • AccessibleName - the name of the property added to the global object
        • only affects static types
      • AllowScriptDispose - if the type implements IDisposable the binding can automatically call Dispose when the JSObject is finalized (deleted or falls out of scope)
        • only affects instance types
      • AllowInheritedMembers - an option to specify whether inherited members should or shouldn't be reflected
  • SMProperty
    • Attribute used to control property embedding options
    • This attribute is used on Fields, Properties and Events
    • Named parameters are:
      • Name - ths script-visible name of the property (if required to be different from the declaration)
      • Enumerate - specifies whether the property is visible during for/in loops
  • SMConstructor
    • Attribute used to control constructor embedding options
    • Named parameters are:
      • Script - tells the binding to prepend the constructor's arguments with the calling script (SMScript instance)
        • The arguments are prepended during method binding and does not require an extra argument to be passed from Javascript.
        • (if set to true, the constructor's first argument must be of type SMScript)
  • SMMethod
    • Attribute used to control method embedding options
    • Named parameters are:
      • Name - the script-visible name of the method (if required to be different from declaration)
      • Script - tells the binding to prepend the method's arguments with the calling script (SMScript instance)
        • The arguments are prepended during method binding and does not require an extra argument to be passed from Javascript.
        • (if set to true, the method's first argument must be of type SMScript)
      • Enumerate - specifies whether the method is visible during for/in loops
      • Overridable - specifies that a script can override the method
        • if the method is overriden by a script then the method is no longer called by the binding but the javascript function is called instead
        • useful for declaring default behavior for callbacks etc.

//defining a static class with a different property name than it's typename
//this class would be accessed from script using 'StaticClass.someMethodOrProp'
//evaluation to a string will return "[object MyStaticJSClass]"
[SMEmbedded(AccessibleName = "StaticClass")]
static class MyStaticJSClass
{
    //defining a property with a different name and set to not enumerate (default is true)
    [SMProperty(Name = "myProperty", Enumerate = false)]
    public static int MyProperty { get; set; }

    //defining a readonly property 
    public static int MyProperty2 { get { return 10; } }

    //defining a method that needs a reference to the script that's invoking it
    //used from script by calling MyMethod() (note the script is not included as a parameter here)
    [SMMethod(Script = true)]
    public static void MyMethod(SMScript script) { }
}

Initializing scripts:

//Initialization using a default (empty) global object
SMScript script = runtime.InitScript("myScriptName.js");

//Initialization using a defined global object
SMScript script = runtime.InitScript("myScriptName.js", typeof(MyGlobalObject));

Note: any type capable of being embedded may also be used as a global object, though only static members are used

Compiling, Executing, and Evaluating scripts:

Code evaluation is possible before a script has been Compiled and Executed.

//Default evaluation
string result = script.Eval("my js code");

//Default evaluation with success notification
bool success = false;
string result = script.Eval("my js code", out success);

//Evaluation with attempted return type conversion
bool result = script.Eval<bool>("my js code");

//Evaluation with attempted return type conversion and success notification
bool success = false;
bool result = script.Eval<bool>("my js code", out success);

//Script compiling and executing
bool compiled = script.Compile("my js code");

//Execution without a return type
if (compiled && !script.Execute())
{
    script.Dispose();
    /* do something with failed execution */
}

//Execution with an attempted return type conversion
if (compiled)
{
    bool success = false;
    bool result = script.Execute<bool>(out success);

    if (!success)
    {
        script.Dispose();
        /* do something with failed execution */
    }
}

Calling functions:

Function calling has 8 usable overloads available from SMScript:

//The following 4 functions will call a function found at script scope (on the global object)

object CallFunction(string name, params object[] args);
object CallFunction(string name, out bool success, params object[] args);

T CallFunction<T>(string name, params object[] args);
T CallFunction<T>(string name, out bool success, params object[] args);

//The following 4 functions accept an IntPtr this refers to the JSObject* of which to call the function on

object CallFunction(string name, IntPtr obj, params object[] args);
object CallFunction(string name, IntPtr obj, out bool success, params object[] args);

T CallFunction<T>(string name, IntPtr obj, params object[] args);
T CallFunction<T>(string name, IntPtr obj, out bool success, params object[] args);

Note: to obtain a pointer to call a function on, both SMRuntime and SMScript define helper functions
  • SMScript - GetPrototypePointer(Type type);
    • for obtaining the JSObject* pointer used to call a function on a static object
  • SMScript - GetInstancePointer(Object obj);
    • for obtaining the JSObject* pointer used to call a function on an instance object
SMRuntime helpers have the same names and functionality but require an SMScript as the first argument

Passing functions as arguments to .net methods:
Functions can be passed safely from Javascript to .net methods thanks to a wrapper object called SMFunction. It overrides it's ToString method to offer both Javascript ToString ability as well as .net ToString. SMFunction provides a wrapper around any Javascript function and can be invoked using:

SMFunction.Invoke(params object[] args)

//Accepting functions as arguments
public static void MyMethod(SMFunction func)
{
    //Invoking the function
    func.Invoke("this is an argument", "this is another argument");
}

Type Conversions:

Type conversions are done implicitly and to the most appropriate match.
For method binding on overloaded methods, explicit conversions are attempted first, followed by implicit conversions.

Basic operation:
  • For integral types, overflows are not allowed and considered failed conversions
    • Example: passing 65536 to a method expecting a ushort as a parameter will fail, reporting a javascript error
  • For object types, conversions to base types and implemented interfaces will succeed
    • Example: passing a Stream to a method expecting IDisposable will succeed
  • For enum types, conversions are handled by determining it's underlying type

Conversions have been applied to best emulate conversion abilities within the .net framework, but without throwing exceptions that can seriously degrade performance.

IEnumerable Interfaces:

IEnumerable interfaces are treated very similar to the javascript Array class, when a type derived from IEnumerable is used in a for/in or for each/in loop. The binding will enumerate over the items of the collection, instead of the properties or fucntions.

Delegates and Events:

Delegates:

Delegates are instanciated like any other type, passing a function as the constructor argument. In order for the delegate to properly invoke the function it points to, parameter count must match.

//javascript
x = new EventHandler(function(s, e) { /*do something*/ });

Events:

Events exposed by .net types are treated like properties and wrapped in a class called SMEvent. This class exposes some helpers to make subscribing to events easier to identify.
(Function variables are automatically converted to the proper delegate, passing delegate types as a parameter will fail.)

//pseudoclass
class SMEvent {
    public string handlerType { get; } //gets the eventhandler type-name
    public string handlerSig { get; } //gets the eventhandler signature (return / argument types)
    public SMEventHandler[] handlers { get; } //gets an array of event handlers subscribed to the event
    public bool addHandler(SMFunction); //adds a handler to the event
    public bool removeHandler(SMFunction); //removes a handler from the event
    public bool removeHandler(SMEventHandler); //removes a handler from the event
}

Using events from script:
//javascript - assumes type 'EventHandler' delegate

//add a handler
someClass.someEvent.addHandler(function(s, e) {  }); //this method has to be removed using the 'handlers' property
someClass.someEvent.addHandler(myHandlerFunc); //this can be removed again later

//remove handler
someClass.someEvent.removeHandler(myHandlerFunc);

var handler = someClass.someEvent.handlers[0];
someClass.someEvent.removeHandler(handler);
//or
someClass.someEvent.removeHandler(handler.function);

Generic Types:

  • 1.5 Alpha

Generic types are very tricky to mimic from javascript. In order to do this, type-names are edited on the fly to reflect how many generic parameters as type has.

Example:
List<T> becomes List$
Dictionary<TKey,TValue> becomes Dictionary$$
Func<T1,T2,TRet> becomes Func$$$
... and so on ...

Unfortunately, without being able to extend the syntax. This seems like the best alternative. To instanciate generic types from script, the generic type arguments must be supplied as parameters to the constructor. These must come before any actual parameter. The parameters that are accepted are any javascript standard class (Array, Boolean, String, Date, Math, Number, etc). Those types are automatically replaced with their .Net equivalent. Custom objects may also be used, as long as they do not have the same name as a javascript standard class. Primitive .Net types cannot be used as they're handled internally. Only object types that have been embedded will be resolved properly.

Using generic types in script:
//Creating a generic list of String
var myList = new List$(String);
myList.Add("MyString");
myList.Add("MyOtherString");

//Creating a generic predicate to do a Find operation on the list
var predicate = new Predicate$(String, function(s) { return s == "MyString"; });
var result = myList.Find(predicate);

//Enumerate our IEnumerable List<String>
//will print both property-name and value
for (i in myList) print(i + " = " + x[i]);

//will print only the value
for each(i in myList) print(i);

Object Locking:

Occassionally an embedding will have the requirement to create and GC lock certain instance types.
To do this the embedding application can use the following methods:

/* script initialization */

//protect object from garbage collection
script.CreateObject(myObjectInstance);

/* script usage, execution, evaluation etc */

//allow object to be elligible for garbage collection
script.DestroyObject(myObjectInstance);

Notes: CreateObject will only create a new JS object if one doesn't already exist. If an object already exists then it will only be GC locked. Locks are applied incrementally. Once the lock counter reaches 0 (or the script is disposed), the GC lock is removed and the object becomes subject to garbage collection.

Garbage Collection Tips:

For performance reasons the binding uses JS_MaybeGC in many places instead of forcing GC with JS_GC.
To force garbage collection it can be done from SMRuntime or SMScript using the GarbageCollect() method.
  • Force garbage collection outside of any embedded functions that might call back into the API.
    • this prevents garbage collection from being called too often, which can seriously degrade performance

A Bad Example:

[SMEmbedded(Name = "MyScript", AccessibleName="Script")]
static class Script
{
    public static string Eval(string name, string code)
    {
        //this assumes a static property on the 'Program' class of a console application
        //Scripts are also stored as a ReadOnlyCollection<SMScript> in SMRuntime
        SMScript script = Program.JSRuntime.FindScript(name);
        if (script == null) return null;

        string result = script.Eval(code);

        // This is bad, the script may nest Script.Eval calls many times, or execute them on a loop
        // this can have a serious impact on performance (Spidermonkey GC is very processor intensive)
        script.GarbageCollect();

        return result;
    }
}

As a solution to the above bad example. Place the call to GarbageCollect outside of whatever method call led to the invocation of Script.Eval.

For example:
public static void Main(string[] args)
{
    /* Initialization code */

    while (true) 
    {
        Console.Write("js> ");

        string code = Console.ReadLine();
        if (code == "exit") break;

        string result = script.Eval(code);

        if (!String.IsNullOrEmpty(result))
            Console.WriteLine(result);

        script.GarbageCollect();
    }

    /* Finalization code */
}

Note: with CompartmentalGC turned off calling a script's GarbageCollect() routine will force GC on the entire runtime

Last edited Nov 16, 2012 at 9:35 PM by abstraktmethodz, version 14

Comments

No comments yet.