Click here to Skip to main content
15,881,803 members
Articles / Programming Languages / C#

Adding JavaScript scripting support to one application: One easy way

Rate me:
Please Sign up or sign in to vote.
5.00/5 (5 votes)
2 Sep 2013CPOL11 min read 29.1K   512   22   6
One simple approach for adding JavaScript scripting support to your .Net application is described.
 


Image 1

Introduction   

This article describes one of many solutions available for adding scripting support to .Net applications, JavaScript scripting in this case. The intention is to share it with other developers that could some how be in the same situation: needing to add scripting support to one application and having near-zero time to implement it.

Motivation 

Firmware development for embedded devices involves several important tasks. Not only requirements definition and system design should call the developers attention, the whole system (firmware included) must be designed to be tested.  

It is not rare during firmware development to introduce major changes in the internal core or basic components. This changes most of the time invalidate all the assumptions made before about code correctness. New bugs may have been introduced in the code so everything must be tested after almost every change.  

Developing the firmware for embedded devices may have one very strict constrain: memory size is too small, and too often limited to several (a few) kilobytes. Under that circumstance it is sometimes not possible to include a full set of unit test code as part of the firmware application, thus greatly limiting system testability.  

Main motivation for this work was moving most of the tests support code and framework to the development workstation side, defining the test cases using one scripting language to avoid recompiling the whole tests suite (application) on every new test addition or modification. Advantage is taken from the fact that the embedded device under tests implements NFC for being controlled from the host workstation or even from another NFC enabled device.

Also of interest was to invest as minimum time as possible to get it up and running. Two hours at most was the desired limit.  

Disclaimer

It should be admitted that this approach is far (far far far away....) from being either the optimal or the best among all currently available solutions, but it was fairly easy to implement by using two freely available components.   

Regarding the JavaScript engine and the editor used for building the application, all credit should entirely go to those component's respective authors and we thank them for making their work freely available. 

JavaScript or C# scripting?

There're many articles covering the process for adding C# scripting to .Net applications. C# scripting may even be more flexible than JavaScript scripting, but after all it was less fun and would probably take more code and time. 

Several (or even many) open source projects exist for embedding JavaScript engines in .Net applications. Next ones are just a few among all. Javascript.Net[^] seemed to be simple to use and implements Google V8[^], but from some comments out there it seems not being very active now. ReoScript[^] was also evaluated, but implements a limited set of JavaScript.

Some useful information about wrapping the Windows Script Engines[^] can be found here. Another implementation, also named Javascript.Net[^] seemed to be very complete, but at the end it was too complex to gather all the pieces together and time was simply running out.  

Thanks to Paul Bartrum's and Sèbastien's work, project Jurassic[^], also seemed to be very easy to integrate and directly compiles the JavaScript source code instead of being merely an interpreter. It also supports the ECMAScript 3 and ECMAScript 5 functionalities.  For it's simplicity at first sight and preliminary tests, Jurassic was selected for the JavaScript execution engine. 

Jurassic Integration into the Application

Referencing the Library

Jurassic integration is a straight forward process, download the precompiled binary (in case you don't want/need to play around with the source code) from the download page (here) and add a reference to it in your application project (in the solution explorer, expand your project tree and right-click over 'References', select the 'Add Reference...' context menu action):  


Image 2

 

In the Add Reference dialog, select the 'Browse' tab and navigate to where the Jurassic library DLL was downloaded:

Image 3

Using Jurassic from Code 

The Jurassic documentation can be found in the project's documentation page [^]. Several examples demonstrate usage scenarios and common tasks.  There is no manual, only sample code but with good descriptions.

The execution engine is implemented in the class Jurassic.ScriptEngine. A few more classes control how the executed scripts interact with the application.  All Jurassic classes and types can be found under the Jurassic namespace.

Using the Execution Engine (for Beginners)

Expressions evaluation and scripts execution are both supported by the engine. Expressions are evaluated using the Evaluate() method from the Jurassic.ScriptEngine class.

Script execution can be done with the methods Execute() and ExecuteFile() from the Jurassic.ScriptEngine class. The Execute() method takes directly the script source code, the ExecuteFile() method takes the name of the file that contains the script source code.

Sample Code 1: Expression Evaluation

The simplest example is evaluating an expression to see the result. The ScriptEngine.Evaluate() method from the class Jurassic.ScriptEngine is used for this task like in the following code.

C#
private void SampleCode1_SimpleEvaluation()
{
    // Instantiate the execution engine.
    Jurassic.ScriptEngine Engine = new Jurassic.ScriptEngine();
 
    // Evaluate the expression and output the result (43) to the debug output console
    System.Diagnostics.Trace.WriteLine( Engine.Evaluate( "((3 + 11) * 3) + 1" ) );
}

More complex expressions can also be evaluated.   

Sample Code 2: Script Execution

For executing one script directly in source code format the <code>ScriptEngine.Execute() method is used, as shown in the following code snipped.

C#
private void SampleCode2_ScriptExecution()
{
        // Instantiate the execution engine...
        Jurassic.ScriptEngine Engine = new Jurassic.ScriptEngine();
 
        // ...and give it access to the application console.
        Engine.SetGlobalValue( "console", new Jurassic.Library.FirebugConsole( Engine ) );
 
        // Execute one simple script, it will output the text "HelloWorld" to the debug console.
        Engine.Execute( 
           "hiThere( 'Hello World!' ); function hiThere(msg) { console.log( msg ); } " 
        );
}	 

Please, note the inclusion of one call to the <code>ScriptEngine.SetGlobalValue() method. It has the intention of giving the script access to the application's output console so the script can have side effects. Information about this can be found in the Jurassic's documentation page Using The Console API[^].

Accessing .Net Class Methods from JavaScript

.Net class methods can be made available for being directly invoked from JavaScript code using the <code><code>ScriptEngine.SetGlobalFunction() method. This function takes two parameters: the name for the global function (in JavaScript) and the delegate that encapsulates the .Net function call. 

Sample Code 3: Exposing one Function to JavaScript

In this sample code, access to showing alert messages using the standard MessageBox.Show() method is implemented.

C#
private delegate void DAlertDelegate( string Message );
private void SampleCode3_ExposingNetFunction()
{
    // Instantiate the execution engine...
    Jurassic.ScriptEngine Engine = new Jurassic.ScriptEngine();
 
    // ...and expose one .Net function to JavaScript
    Engine.SetGlobalFunction( "alert", 
       new DAlertDelegate( ( msg ) => { MessageBox.Show( this, msg ); } ) 
    );
 
    // Call the function from the script, it will show one message box 
    // with the text "Hi there!".
    Engine.Execute( "alert('Hi there!')" );
}

Please, note the use of the lambda expression ( msg ) => { MessageBox.Show( this, msg ); } for implementing the pop-up alert message. It was used there only for test purposes, but note it only works as intended if the Engine.Execute() method is called from application's main thread (take a look at how this is implement in the sample application that accompanies this article).

Exposing .Net Objects and it's Methods to JavaScript 

.Net objects can be exposed to JavaScript code using the <code>ScriptEngine.SetGlobalValue() method (just like shown above in Sample Code 2).  The process for making fields and methods visible to JavaScript code is better described in the Jurassic's documentation page Exposing a .NET class to JavaScript[^]. Following code makes just a simple demonstration.

Sample Code 4: Exposing a .Net Class to JavaScript 

The following code contains the declaration and implementation of on sample class, SampleExposedClass,  which exports one method, SayThis(), to JavaScript.

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Jurassic.Library;
using System.Windows.Forms;
using Jurassic;
 
namespace JavaScript_EasyWay
{
    /// <summary>
    /// This class demonstrates how one .Net class can be exposed to JavaScript
    /// </summary>
    public class SampleExposedClass : ObjectInstance
    {
        /// <summary>
        /// This is used only for passing it to MessageBox.Show();
        /// It is not mandatory.
        /// </summary>
        private Form m_MainForm;
 
 
        public SampleExposedClass( ScriptEngine Engine, Form MainForm )
            : base( Engine )
        {
            m_MainForm = MainForm;
            PopulateFunctions();
        }
 
 
        /// <summary>
        /// This is the function to be automatically exposed to JavaScript.
        /// </summary>
        /// <param name="Message">Message to be shownto the user.</param>
        [JSFunction( Name = "SayThis" )]
        public void SayThis( string Message )
        {
            MessageBox.Show( m_MainForm, Message );
        }
    }
}

Using the class from JavaScript is shown in the following code snipped: 

C#
private void SampleCode4_ExposingNetClass()
{
    // Instantiate the execution engine...
    Jurassic.ScriptEngine Engine = new Jurassic.ScriptEngine();
 
    // ...and expose one .Net class instance to JavaScript
    Engine.SetGlobalValue( "MyExposedNetObject", new SampleExposedClass( Engine, this ) );
 
    // Call the function from the script, it will show one message box with the text 
    // "Hi there!".
    Engine.Execute( "MyExposedNetObject.SayThis('Hi there!')" );
}

More on Exposing .Net Objects to JavaScript

The Jurassic  JavaScript engine also provides support for creating .Net class instances from JavaScript with operator new. More information and sample code for that advanced topic can be found here[^], in the section named Building an Instance Class.

Going a Little Bit Further 

Just adding JavaScript execution support was not enough for the test application, having edition capabilities and tests results reports capture were also needed.

Writing to the application's console from JavaScript was the simplest method found for generating information about the running tests and results. Jurassic directly provides one class for logging text messages with similar interface to FireBug's console.log().

Adding JavaScript Edition Support

As the good thing of using the scripting language is not having to compile the whole application on every script's source change, adding script edition capabilities was of great interest. Of course, syntax colouring is currently something that every editor should have and the editor of choice was the Fast Colored TextBox for Syntax Highlighting[^] , from Pavel Torgashov. It is one relatively easy to use component that includes built-in colouring support for the JavaScript language, among many others.  

Capturing the Application's Console Output

For capturing the application's console output it's a matter of output redirection. Firstly the console's output stream (property Console.Out) must be saved so it can be restored at later time if needed. Redirection can be done so the console's output is written to one file, but in this case it is interesting to simply capture and show this output in the same form using one TextBox or something similar. 

Redirecting the output stream so it is written into one memory buffer is straight forward, as shown in the following code snipped:  

C#
// First, back up the current output stream.
TextWriter COutBackup = Console.Out;
 
// Create one new memory buffer stream...
MemoryStream MStream = new MemoryStream();
StreamWriter Writer = new StreamWriter( MStream );
 
// ... and assign it as the console's ouput stream
Console.SetOut( Writer );
 
try
{
    // todo: include the script execution code here
}
catch
{
}
 
// Restore the output stream...
Console.SetOut( COutBackup );
 
// ... and recover the captured output...
Writer.Flush();
MStream.Seek( 0, SeekOrigin.Begin );
 
StreamReader Reader = new StreamReader( MStream );
 
// ...which will be held in this variable (or where ever it's ok for the application)
String CapturedOutput = Reader.ReadToEnd();
Writer.Close();


This method of redirection has one drawback, by design the MemoryStream class holds only one stream location pointer and uses it both, for writing and reading. That makes not possible to make concurrent reads and writes to the stream so recovering the captured console output must be done after script execution has completed. One improved method was used as described below.

Improved Console Output Capture 

As only outputs to the console are of concern for the application, one unidirectional communication pipe could do the job and make it possible to concurrently read from the captured console output. 

The class RedirectionStream, shown in the following code snipped, was written for this purpose. Only the relevant and required methods were written or overridden. Better solutions (as, perhaps, using NamedPipe in .Net 4.0) can certainly be found, but one very simple approach is to relay on using one Queue<byte> and protecting accesses using the C# built-in lock mechanism. 

This class can largely be improved by using Monitors and Semaphores for signaling when more data is available, but there was no need for that in the device firmware tests application. It could also be implemented as one single circular pre-allocated buffer, but it could have taken more time to develop.

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
 
namespace JavaScript_EasyWay
{
    public class RedirectionStream : Stream
    {
        private Queue<byte> m_Buffer;
 
 
        public RedirectionStream()
        {
            m_Buffer = new Queue<byte>();
        }
 
 
        public override bool CanRead
        {
            get { return true; }
        }
 
        public override bool CanSeek
        {
            get { return false; }
        }
 
        public override bool CanWrite
        {
            get { return true; }
        }
 
        public override void Flush()
        { }
 
        public override long Length
        {
            get { return m_Buffer.Count; }
        }
 
        public override long Position
        {
            get
            {
                return 0;
            }
            set
            {
                throw new NotImplementedException();
            }
        }
 
 
        public override int Read( byte[] buffer, int offset, int count )
        {
            int ReadBytes;
 
            if (buffer == null)
                throw new ArgumentException( "Output buffer can not be null" );
 
            if ((offset < 0) || (offset + count > buffer.Length))
                throw new ArgumentException( "Trying to write out of bounds" );
 
            if (count < 0)
                throw new ArgumentException( "Invalid count" );
 
            ReadBytes = 0;
 
            lock (m_Buffer)
            {
                while ((count-- > 0) && (m_Buffer.Count > 0))
                {
                    buffer[ offset++ ] = m_Buffer.Dequeue();
                    ReadBytes++;
                }
            }
 
            return ReadBytes;
        }
 
        public override long Seek( long offset, SeekOrigin origin )
        {
            throw new NotImplementedException();
        }
 
        public override void SetLength( long value )
        {
            throw new NotImplementedException();
        }
 
        public override void Write( byte[] buffer, int offset, int count )
        {
            if (buffer == null)
                throw new ArgumentException( "Input buffer can not be null" );
 
            if ((offset < 0) || (offset + count > buffer.Length))
                throw new ArgumentException( "Trying to read out of bounds" );
 
 
            bool More = (count > 0);
 
            lock (m_Buffer)
            {
                while (count-- > 0)
                {
                    m_Buffer.Enqueue( buffer[ offset++ ] );
                }
            }
 
            if (More && (DataReady != null))
            {
                try
                {
                    DataReady( this, EventArgs.Empty );
                }
                catch
                { }
            }
        }
 
 
        public event EventHandler DataReady;
    }
}

Sorry for the lack of comments in previous code.

Using the RedirectionStream class show above as the base stream to receive the console's output, execution can be done in a worker thread while the main application thread continues with it's main job: guarantee the application responsiveness to the user.  Text strings written to the console's output can be retrieved either periodically (using a Timer in the form) or immediately by handling the RedirectionStream.DataReady event (as shown in the sample application). 

What if the Engine Hangs?

One point to take into account with the Jurassic JavaScript engine is it does not directly support interrupting the script execution.

With this simple script it is possible to hang the engine:

JavaScript
// This will hang the engine until the execution thread
// is Aborted
while (true);

Interrupting the execution of one irresponsive script is only possible if the execution engine is running in a separate thread that can be aborted using the Thread.Abort() method.

Sample Application

Rather than posting all the code that demonstrates the techniques described here, one completely functional and extendable sample application has been developed using part of the code produced for the embedded devices firmware validation and test application mentioned above.

The sample application demonstrates how it is possible to accomplish the following: 

  • Embedding the Jurassic JavaScript execution engine in the application (or even other engines);
  • Adding support for scripts edition, syntax colouring included;
  • Usage of worker threads for executing the script;
  • Script execution abortion;
  • Console's output redirection;
  • Unidirectional stream class creation for thread-safe interprocess communication.

The following screen capture shows one script execution in action. The sample script shown firstly prints out one message to the console and then calls one .Net function which implements the alert() JavaScript function. After warning about what will happen later and what to do, the script blocks the JavaScript execution engine using one infinite loop.

One menu item is provided for cancelling the current script execution.

The screen shot shown at the top of the article corresponds to the test application developed using the techniques described here. The following was taken from the demonstration application that is provided in source code with this article.

Image 4


No support was included in the application for saving/opening scripts into the editor. Only one very simple functionality has been added so the script is written to file on every execution and restored at application start.

Points of Interest    

Many JavaScript execution engines are currently available so deciding which one to use is some times not so simple task. 

Up to the extent of what has been tested during the firmware test application development, and what is currently in use, Jurassic performs fairly well. At the beginning it seemed to be slow at execution start of large script files, when the engine compiles the JavaScript source into IL language. Perhaps this feeling comes from the fact that, used to real-time programs that run in microseconds time (or milliseconds, for the sloooooooow ones), one second seems like an eternity when waiting for the engine to compile the code and run it.

One last thing, when passing .Net object arguments in function calls between JavaScript and .Net care must be taken to check they are Supported Types[^]. Some types not described in the documents page are mentioned in the project's Discussions[^] page.

History 

  • September 3rd 2013: First Version.

 








License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Spain Spain
V.Lorz has been 'burning' transistors, diodes and chips from late seventies/early eighties. As a Electronics Engineer, computers and microprocessors programming started being a hobby, for a short while were the perfect tool for testing the hardware and soon became a passion. Basic, Assembler, Pascal, C and Object Pascal were the first languages he used. He currently develops embedded applications using C/C++ and desktop applications with C# and C++.Net.

Among many other things, V.Lorz has been dedicated to designing, prototyping and programming custom electronic devices for data acquisition, signal processing, RFID, access control, industrial instrumentation and biomedical applications, using C/C++, C# and VHDL as main programming languages for software and hardware development.

V.Lorz is currently employed as R&D Manager in a firm near Barcelone.

Comments and Discussions

 
GeneralMy vote of 5 Pin
Yasushi Irizawa9-Jan-14 5:18
Yasushi Irizawa9-Jan-14 5:18 
GeneralRe: My vote of 5 Pin
V.Lorz9-Jan-14 19:53
V.Lorz9-Jan-14 19:53 
Question^5 thanks Pin
BillWoodruff9-Oct-13 15:13
professionalBillWoodruff9-Oct-13 15:13 
AnswerRe: ^5 thanks Pin
V.Lorz9-Oct-13 19:21
V.Lorz9-Oct-13 19:21 
Questionmy 5... Pin
pl.29-Sep-13 1:53
professionalpl.29-Sep-13 1:53 
AnswerRe: my 5... Pin
V.Lorz9-Sep-13 2:44
V.Lorz9-Sep-13 2:44 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.