September 2007 - Posts - Colin's Blog

September 2007 - Posts

Adding JScript.Net Scripting to C# Application

Download Code: CsJScriptDemo.zip 46KB

I didn't find much on the 'net when I was looking for ways to do this so here is my solution for adding basic scripting functionality to a C# application with an object model. This method uses CodeDOM to generate and compile a JScript.net class on the fly, using the classes and interfaces from the C# application. It uses .Net 2.0, and a Visual Studio 2005 solution is provided.

For the purposes of this exercise, I have a windows forms application as shown below. The application has a Listbox bound to a collection of items on the left, a Textbox to type scripts into on the right and a Button to execute the script.

jscsapp

For the object model I have 2 objects:

  1. Application has a Title property that changes the title of the main form, and a generic collection of Item objects which is bound to the list on the left.
  2. Item has a single property Text.

Jumping into the code, the Item and Application objects are shown below. Note that both objects inherit from JSObject to get the associative array behaviour that you would expect in javascript, but it is not required. The Application constructor has a reference to the Form object so that we can change the text.

public class Item:Microsoft.JScript.JSObject { private string _text; public Item(string text) { _text = text; } public string Text { get { return _text; } set { _text = value; } } } public class Application:Microsoft.JScript.JSObject { private Collection<Item> _items; private System.Windows.Forms.Form _form; internal Application(System.Windows.Forms.Form form) { _items = new Collection<Item>(); _form = form; } public Collection<Item> Items { get { return _items; } } public string Title { get { return _form.Text; } set { _form.Text = value; } } }

The main form has an instance of the Application object which will be passed to the script when it is executed.

private Savage.CsJScriptDemo.Model.Application _application; public Form1() { InitializeComponent(); _application = new Savage.CsJScriptDemo.Model.Application(this); UpdateList(); } private void UpdateList() { this._listBox.DataSource = null; this._listBox.DataSource = _application.Items; this._listBox.DisplayMember = "Text"; }

To execute the script I have a single method on an interface called ICompiledScript.Go which takes the Application instance. You can change this or add more methods that with different arguments and return types if you need to. The JScript will be compiled into a class that implements that interface, so that we don't need to use invoke to call the methods.

ICompiledScript compiled = ScriptCompiler.Compile(_scriptTextBox.Text); compiled.Go(_application); UpdateList();

I'll refer you to the source code attached for the entire ScriptCompiler class because CodeDOM is quite verbose as you can see from the fragment below:

//could easily be C# or VB CodeDomProvider codeProvider = new JScriptCodeProvider(); //Namespaces CodeNamespace codeNamespace = new CodeNamespace("some.arbitrary.name.space"); codeNamespace.Imports.Add(new CodeNamespaceImport("System")); codeNamespace.Imports.Add(new CodeNamespaceImport("Savage.CsJScriptDemo.Model")); //Scripting Class CodeTypeDeclaration codeTypeDecl = new CodeTypeDeclaration(className); codeTypeDecl.IsClass = true; codeTypeDecl.BaseTypes.Add(typeof(object)); codeTypeDecl.BaseTypes.Add(typeof(ICompiledScript)); codeNamespace.Types.Add(codeTypeDecl); //...snip

There are no special tricks in the ScriptCompiler class, it creates a class that implements ICompiledScript, adds a constructor and the Go method containing the script provided. The assembly is then compiled in memory and an instance of the class returned.

There are some quirks in this method that you'll need to experiment with for your application e.g.

The case of toString() is ToString() so either implement the other method or use a regex to replace the offending text before compiling the script.

You will run into code access security permissions at some point if you are accessing files or UI elements etc.

Your JScript class cannot inherit from a C# class but you can implement interfaces.

Because you are referencing the C# assembly, all the public classes exposed in that assembly are available to the JScript (if the user knows the namespaces) so be careful what you make available. You could also use regex to check for those kind of things before you compile.

Posted by colin | 1 comment(s)
Filed under: ,

WiX v3.0, LGHT0217 error and Light.exe unhandled exception on Vista

Here's 2 tips for developers using WiX 3.0 on Vista:

  1. If you are getting error MSB6006: "Light.exe" exited with code -532459699  or light.exe crashes with an unhandled exception and you are running v3.0.2925.0 or earlier build, get one of the later nightly builds from http://wix.sf.net/releases. I used v3.0.3307.0 and that fixed the problem.
  2. If you are getting this error: light.exe : error LGHT0217 : An unexpected external UI message was received: The installer has encountered an unexpected error installing this package. This may indicate a problem with this package. The error code is 2738. I found the answer here, it turns out that vbscript.dll is not registered and some of the installer validations are written in vbscript. To fix the problem, open a command prompt as administrator, and run the following command:

C:\>regsvr32.exe C:\Windows\System32\vbscript.dll

Posted by colin | 1 comment(s)
Filed under: