I always have ideas for making FlexWikiPad better kicking around in the back of my head. Of course, I'm still cranking on getting basic functionality in place, so it'll be a long time before some of these ideas get realized, but it's still fun. One of the ideas that I thought about this morning was how to add macro support to the app, so people could add a little automation if they wanted to. Having been exposed a bit to Javascript lately, I've decided that it is an excellent language, with a great balance between power and ease of use...so it seems like a good choice for a scripting language to use from within FlexWikiPad. On thinking about it a little more, I decided that an even better choice is JScript.NET, which combines the niceties of the Javascript language with the power of the .NET libraries. Sweet.
I set out this morning to see if I could figure out what it would take to weld JScript.NET into a Windows Forms application. As it turned out, it was so easy to do, I didn't even need to look on the Internet to find one of the dozens of other implementations of this that must already exist. And it's too cool not to share. So here's the code:
using System.CodeDom.Compiler;
using System.Reflection;
using Microsoft.JScript;
// Create the compiler object
JScriptCodeProvider provider = new JScriptCodeProvider();
ICodeCompiler compiler = provider.CreateCompiler();
CompilerParameters options = new CompilerParameters();
options.GenerateInMemory = true;
// Generating an "executable" just means that the code will have an entry point, rather than simply
// being a collection of classes.
options.GenerateExecutable = true;
// Adding references is optional - mscorlib and Microsoft.JScript are automatically referenced
options.ReferencedAssemblies.Add("System.Windows.Forms");
CompilerResults results = compiler.CompileAssemblyFromSource(options, textBox1.Text);
// Run the newly generated assembly's Main method
Assembly assembly = results.CompiledAssembly;
MethodInfo entryPoint = assembly.EntryPoint;
entryPoint.Invoke(null, new object[] { null } );
And that's it. The code above is the handler for a button I put on a form. The only other thing on the form is a multiline text box that holds the code I want to compile. In my case, I typed in
import System.Windows.Forms;
MessageBox.Show(”Hello world”);
as a simple test, but I could have used any valid JScript.NET program. Note that one of the benefits of JScript.NET is that it doesn't require me to explicitly create a Main method - anything at global scope will automatically be stuffed into one for me automatically. This sort of brevity is a real feature in a macro language, in my opinion.
As far as how to use this to achieve automation, that's the easy part: the way I've written this, the JScript.NET code will run in the same AppDomain as the application code that's calling it. So they can both easily access static members of any types they can both access. One way to handle this is to add this line of code
options.ReferencedAssemblies.Add(Assembly.GetExecutingAssembly().Location);
just before you call CompileAssemblyFromSource. That will make the generated assembly depend on the EXE that's calling it, allowing it to use any types that are defined within the exe. So for instance, a class like this in the application
public class Context {
private static Context _context;
private string _data;
public static Context Current {
get {
if (_context == null) {
_context = new Context();
}
return _context;
}
}
public string Data {
get { return _data; }
set { _data = value; }
}
}
Would let me write macro code that looks like this
Context.Current.Name = "Craig";
and after the macro ran, I could retrieve the value “Craig” from within the EXE by simply reading Context.Current.Name. Or I could do the reverse, and set values in Context.Current before invoking the macro, and it would be able to read them. The possibilities are endless.