Introduction
I wanted to take a break from building parsers and such and do something a little different, so here's a handy little library I've whipped up to add scripting support to your .NET applications.
Occasionally, I've found the need to execute JavaScript in my apps, sometimes for scraping, sometimes just to have JS scripting support (VBScript works too) and at least once for legacy ASP emulation.
Microsoft provides COM interfaces to use these but using them from .NET is ugly.
Luckily, this little library makes it easy.
Conceptualizing this Mess
Microsoft provides an extensible scripting framework they use in legacy ASP pages and in Internet Explorer. By default, Windows ships with VBScript and JavaScript (JScript) but 3rd parties have added others.
These script engines are COM visible and expose their scripts as COM objects that can call your code or be called from your code. The only trouble is the interfaces are messy.
What I've done is wrapped these interfaces in two easy to use objects, ScriptHost
and ScriptEngine
.
Using this Mess
First of all, script engines cannot exist autonomously. They need a place to put down roots. That's what ScriptHost
is for. ScriptHost
manages script engine creation, and lifetime, as well as providing various services to script engines, like error reporting and object retrieval. Before we can create any script engines, we must create one of these lil guys. Remember to dispose of it when you're done. Closing a script host closes all engines that were created by it as well.
Next, we have ScriptEngine
which you can get via ScriptHost.Create()
. These represent autonomous regions of script code. For an idea of what this means, know that each <script>
block in a web page would correspond to one of these, and the page itself would be the ScriptHost
. Pass a language to Create()
to get a script engine in your desired language. There's a "fast" JS engine you can access by passing the ChakraJS
constant but I haven't tested it and I'm not sure how fast it actually is.
With the ScriptEngine
, we can Evaluate()
expressions, Run()
statements, or AddCode()
to the script block. We can also add objects to the Globals dictionary which can then be accessed by name in your script code.
Hopefully, the demo code will make all things clear:
Console.WriteLine("Creating script host");
using (var host = new ScriptHost())
{
Console.WriteLine("Creating javascript engine");
var engine = host.Create("javascript");
Console.WriteLine("Evaluate 2+2: ");
Console.WriteLine(engine.Evaluate("2+2"));
Console.WriteLine("Add code: var i = 1;");
engine.AddCode("var i = 1;");
Console.Write("i = ");
dynamic obj = engine.Script;
Console.WriteLine(obj.i);
Console.WriteLine("Add global app object");
engine.Globals.Add("app", new AppObject());
Console.Write("Run \"app.writeLine('Hello World!');\": ");
engine.Run("app.writeLine('Hello World!');");
}
Note that we've added AppObject
to the script engine above. Here's the definition for it:
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.AutoDispatch )]
public class AppObject
{
public void writeLine(string s) { Console.WriteLine(s); }
}
Note that we've added a couple of attributes from System.Runtime.InteropServices
. This is necessary so that .NET creates the infrastructure needed to let the script communicate with the object via COM/OLE Automation. If you don't add these, your script will not be able to communicate with your object and you'll get an error.
Also note, weirdly, despite each script engine having its own collection of globals, all globals within a script site share the same naming container. That means that only one global per name is honored regardless of what script engine it is created under. In the case where two script engines have an item with the same name, only the first instance will be associated with that name. It's okay to add an object to two or more script engines under the same name. I didn't design it this way - it's part of the design of the Microsoft Active Script framework.
That covers it. It should be simple enough to take it from here and add JavaScript and VBScript scripting to your apps. Have fun!
History
- 14th January, 2020 - Initial submission