Adding scripting to your application can be a good way of providing your power users with advanced control or for allowing it to be extended in ways you didn't anticipate.
Our Color Palette Editor software allows the use of dynamic templates via the use of the Liquid template language by Shopify. This is quite powerful in its own right and I will likely use it again in future for other templating activities. But as powerful as it is, it cannot replace scripting.
I also experimented with adding macro support to our Gif Animator, but given I was too busy making a mess of the program I never got around to releasing the macro extension.
Recently I was working on a simulator sample and ended up integrating part of the aforementioned macro solution so I could set up the simulation via a script. I thought that was interesting enough in its own right to deserve a post.
However, I am not interesting in writing a scripting language. (Actually, that's not entirely true... as soon as a print version of Crafting Interpreters is available I plan on trying to implement it in C#.) As most of the world seems to run on JavaScript these days, it is reasonable to use this as a base. And fortunately, there is a decent JavaScript interpreter available for .NET, namely Jint.
Using Jint
Note: This article is written on the assumption that you are using Jint version 3. However, the basic code will also work with version 2, albeit with a noticeable performance decrease. Unfortunately, there are breaking changes between version 2 and 3 in how you parse the JavaScript so some of the examples in this article may not work directly in Jint 2.
The demonstration that accompanies this article has variants for both Jint versions.
After installing the Jint NuGet package, you could simply execute a script like this
(Example code from the Jint project page)
This is pretty powerful stuff. The SetValue
method can be used
to add .NET object instances to be accessible from JavaScript,
or you could define function methods that can be executed by
JavaScript.
You can selectively include either the full .NET Framework or a
list of "safe" assemblies - this is done via the AllowClr
method when creating the engine.
(Example code from the Jint project page)
It is also possible to add specific types to the engine. These can either then be directly instantiated from a script, or static values accessed.
While the Execute
method can be used to load and run
JavaScript, you can also call individual functions via the
Invoke
method. This could be handy for allowing extensibility
via scripts.
(Example code from the Jint project page)
Safe and secure
By default, Jint doesn't provide access to the full .NET API, and so scripts should be incapable of performing malicious actions. As noted in the previous section you can easily add CLR assemblies or custom objects which could then allow for malicious actions. You should consider how much functionality you wish to expose via scripts and risk assess your application's needs.
When creating an Engine
instance, Jint also allows you to
specify constraints such as how much memory can be used, or
program size, as well as allowing script execution to be
cancelled.
(Example code from the Jint project page)
It is also possible to define your own constraints but this is something I haven't looked into yet.
Creating a base
As I don't really want my applications to have to know about Jint or how it works, I'm going to wrap it around a helper class. This class will take care of managing the scripting engine and providing some common functionality.
I'm making it abstract as the interactivity features will need to be implemented separately for each application type - e.g. once for console applications and once for applications with a GUI.
Initialising the engine
I don't want the JavaScript engine to be created until it is
required, so any method that needs the engine to be present will
first call InitializeEngine
to ensure the instance is created.
I also have a virtual InitializeEnvironment
method that is
called once after the engine is initialised, allowing subclasses
to configure the engine appropriately, for example by making
certain types available, or loading objects for scripting use.
It will always define basic output methods log
and print
(both will perform the same action), and also a cls
method. I
also define three functions to mirror JavaScripts interactive
aspects, naming alert
, confirm
and prompt
.
Note: Although the wrapper class has an
Interactive
property to enable the use of interactive functions, they will still always be defined in the engine so scripts don't crash if interactions are disabled.
Loading a script
Note: This code in this section applies to Jint 3 only. While you can perform the same actions using Jint 2, the API is different.
The easiest way of getting a script into Jint is to call its
Execute
method and pass in a string containing your
JavaScript. However, if you want to perform any sort of
examination of the source, for example to check if a given
function exists, then you need to parse the code.
Fortunately, this isn't something you need to do manually as Jint 3 uses the Esprima .NET library for this, and we can too.
The Script
object has a ChildNodes
property which provides a
full Abstract Syntax Tree (AST) for your script, allowing you to
query the entire program.
For example, consider the following script. Short and simple?
This is condensed example of the AST generated for the above script
Using the AST, you could examine the script before you allowed
it to be ran. For example, you could check for the presence of a
function named main
, and if found, invoke that directly. I
suppose you could also use it to try and ensure a script is
safe, but given the script could be obfuscated I'm not sure how
effective that would be.
Once you have a Program
object, you can load this into your
engine instance by calling its Execute
method the same as you
would with a string.
Ideally, after parsing the script I would check it to ensure that only functions are present and no global statements that will be executed. After all, it is a little odd that in order to load a script you actually have to execute it.
The try
blocks are a bit off putting too, especially as I will
be doing the same "pattern" elsewhere in the class. Sometimes
when confronted with this I tend to have a method that accepts
an Action
and therefore only have the boilerplate in one
place, however to keep this example simple (if slightly more
verbose), I have choose to keep it as is.
Script execution
Our scripting object will expose three different execution
functions - Execute
, Evaluate
and Invoke
.
The first method, Execute,
will execute-load a script and then
search for a method named main
. If one is found, it will
invoke this. My intended use case for this particular method is
for application plug-ins.
After the script has executed, I get any completion value, convert it to a .NET object and then return it.
The second method, Evaluate
is intended for read, execute,
print, loop (REPL) scenarios... for example an Immediate style
window, or a scripting command interface. It simply
execute-loads the specified script and then returns any result.
The final method, Invoke
is unique in that it assumes that
Load
, Execute
or Evaluate
have been previously called to
load script into the engine. It will then attempt to execute a
named function.
Displaying output
In many cases, it would be beneficial for scripts to be able to
output content, for whatever reason. While I'm not going to try
and reproduce JavaScript's console
object, having a log
function is of great help. In the initialisation section above,
I define both print
and log
as aliases for outputting content.
Most of the functions that must be overridden to provide functionality, be it logging or interactivity, require a .NET string. Therefore we need some code to transform script engine values into a .NET string, including allowing literal strings for null or undefined values.
With these helpers in place, we can define object
-based
methods that are bound to the Jint Engine
instance, and then
translate these into .NET strings before calling the
implementation specific overrides.
Interactivity
Although I'm not going to go out of my way to add a lot of
interactivity to the script engine, mirroring JavaScript's
alert
, confirm
and prompt
methods would be of great use
for some types of scripts.
However, as not all hosts might not support interactivity, or
you may wish to disable it on an ad-hoc basic, I have added an
Interactive
property to the base class. When set to true
,
the interactive methods will work as expected. When false
, no
user interface elements will be displayed, and, in the case of
functions, default values returned.
For a WinForms GUI application, the following overrides can be used to used to present the UI.
Thread safety
When I first added scripting to an application, it ran in the UI
thread. For this demonstration, I decided to run it on a
separate thread using a BackgroundWorker
. I don't really know
if Jint is inherently thread safe or not, but so far I haven't
had any problems with this approach.
Except of course, for when I want to update a WinForms control
as this isn't possible to do on anything but the UI thread. In
addition, the ShowPrompt
example above, if called from another
thread, will neither be modal nor positioned correctly.
Fire and forget
In the simplest of cases, you can check if the call is occurring
on a different thread by using the Control.InvokeRequired
property. If this is false
, the code is executing on the UI
thread and it is safe to continue. If true
, it is on a
different thread and so you need to use the Control.Invoke
method to execute on the UI thread instead.
In the following example, the WriteLine
method will either
update a text box if it is safe to do so, or will invoke itself
on the UI thread if not.
Returning a result
When you need to return a result, it is a little more
complicated, but not overly so. As before, I check
InvokeRequired
to see if the code is on the UI thread and if
not I set up an asynchronous operation using
Control.BeginInvoke
, using an IAsyncResult
instance to wait
for completion and access the result via Control.EndInvoke
.
This API is old, introduced long before .NET's
Task
class. Unfortunately as WinForms has been stagnating since 2005 or so, the chances of Microsoft modernising the API seem slim.
Stepping through the code when an invoke is required - first, I get a delegate representing the function call I need to make.
Next, I begin an asynchronous operation by calling
Control.BeginInvoke
, passing in the delete to execute and the
parameters it requires. This returns an IAsyncResult
instance
which I capture.
This result provides access to a WaitHandle
which in turn
provides a WaitOne
method. By calling this, our non-UI thread
will pause until it receives a signal indicating that the async
operation has completed.
Once the signal has been received and the code continues
execution, we call Control.EndInvoke
, passing in the async
result we captured earlier. The EndInvoke
method will return
the result of the function call which I then cast appropriately.
Finally, I close the WaitHandle
to cause its resources to be
released.
There's quite a lot of boiler-plate code involved, I should probably create extension methods to simplify this for future work.
To multi-thread or not to multi-thread
Depending on what functionality you expose to your scripts, running on multiple threads could be a significant issue as if the UI itself isn't thread aware, then any calls which interact with the UI will either have unexpected results or throw the dreaded Cross-thread operation not valid exception.
Other languages
Although I initially created my scripting experiment using JavaScript, other languages are available. Previously I've used Lua via the VikingErik.LuaInterface package (which I only remember because of the name!). I'd probably use NLua for new work.
Then there is Python. This seems to be becoming more popular (or maybe always was and I was unaware!). IronPython is a .NET implementation of Python and I will probably look into this in future. Last time I took note of this library, it had just been dropped by Microsoft (along with IronRuby), although unlike the latter it appears to have landed on its feet.
The sample application
The demonstration program included with this article is a pretend drawing application. I say pretend as I haven't included anything other than a script interface, but you can "draw" with this, and it was a quick and easy way of showing the functionality.
The application defines a PixelPicture
class, an instance of
which is added to the scripting engine via the picture
variable. This exposes a number of methods such as plot
,
drawLine
, drawCircle
etc to perform drawing methods.
It also defines an application
variable which can be used to
manipulate the host application, for example to change the
window caption. Nothing exotic, but simple ideas on how you
could add scripting to your own applications.
The code for plotting the pixels for lines, circles, ellipses and curves uses Bresenham's line algorithm, with the C# implementation coming from easy.Filter's The Beauty of Bresenham's Algorithm page.
The flood fill implementation was taken from RosettaCode.
While I have provided versions of the sample application using both Jint 2 and Jint 3, I upgraded to Jint 3 after starting to write this article, therefore it is missing refactoring and enhancements made during the course of writing this piece.
You can download the sample projects from the links on this article, or visit the GitHub repository for the latest version.
For example, the following script will draw a smiley.
Or, for something more dynamic, the following script will plot the results of sine and cosine.
Although this demonstration project is a little contrived, if you add scripting support to your application you may find it be a very valuable feature.
Like what you're reading? Perhaps you like to buy us a coffee?
# Kvapu
# Richard Moss
# Dax
# Richard Moss