This article will focus on Visual Studio for Applications, or VSA, and explain how it can be integrated with .NET programs. It will also explain a few key areas you must understand in order to successfully integrate VSA into your programs.
VSA provides an excellent means of providing extensibility to your program by allowing script code to modify your program long after the main object model has been designed and compiled. Allowing your programs to host and run script files is an excellent way to allow others to extend your program in ways you may not have foreseen at its induction. Many times, added functionality is required by end users or high demand clients that require new changes to be recompiled into the old system by its creators. This takes time and money, and is often only necessary because modifications require the use of professional build tools, something many users aren’t skilled at using. However, with a solid object model exposed to a scripting interface, anyone capable of writing macros using a text editor and following an SDK can tweak much of the functionality of your program without the major hassles involved with professional build tools.
VSA replaces the older technology Visual Basic for Applications, or VBA. VSA allows .NET programs to host and compile scripting languages such as VBScript and Jscript. Using VSA, you can create a scripting engine directly inside your .NET programs, and pass your own object instances and assembly references to the engine and script. Using your own custom object model, you can allow the scripts to modify your program after it has been compiled. Anyone with a text editor can follow up and add custom scripts or change existing ones, without having to bother you to make these changes.
There is supposed to be a VSA SDK available for download from the Microsoft Script Center, but I cannot find a link to it. Feel free to look for yourself by starting here at the Microsoft Script Center. The MSDN Library contains some help on the topic but it is limited. As usual, my most valued resource ended up being my old faithful friend Google. Just go out and lookup the various interfaces that I will discuss and you will find plenty of helpful articles.
The following assemblies are distributed with the .NET Framework and should be located in the GAC, or global assembly cache, on your system. Microsoft.Vsa.dll, Microsoft.Jscript.dll, and Microsoft.VisualBasic.Vsa.dll are included with the .NET Framework 1.1, and are readily accessible. Microsoft.Vsa.dll contains the core interfaces and objects needed to implement your own scripting engine and scripting host.
The following assemblies are your required references:
You will need to decide which scripting language your program will support. Microsoft has supplied two engines, one for JScript and the other for VBScript. You may create your own, but that is beyond the scope of this document. The two classes that you may choose from are as follows. Using these classes, you will be allowed to run JScript or VBScript inside your programs. This is the language that you will use to script your program.
Once you have located and added references to the assemblies mentioned above, you need to implement the
IVsaSite interface to allow your program to communicate with the script engine. Implementing this interface allows the engine to notify you of compilation errors, as well as request information such as object instances from your program as they are needed. This is an added bonus as you can defer the creation of objects until they are needed by the engine.
IVsaSite (fully qualified as
Here is a code snippet from my example project that shows how you would implement this interface:
public class VsaScriptingHost : IVsaSite, IDisposable
A scripting engine is a class that implements the
IVsaEngine, as defined in the
Microsoft.Vsa namespace. A script engine is capable of loading, compiling, and running script code. A scripting engine must be initialized before it can be used. Once initialized, code items, global items such as object instances, or references can be added to the engine. With my experiences, the order of initialization must occur in the proper sequence in order to avoid errors.
Creation and initialization of a scripting engine is quite simple. The following steps are a guideline from my experiences that determine the order of operations for initializing a new scripting engine. The order is not concrete, but I have experienced errors when these steps are executed out of the order specified.
Once you are ready, and have determined which type of scripting engine you will use, simply create an instance of the desired class. This new instance will be used as your scripting engine. I typically use a variable of type
IvsaEngine, but you may use the actual type of engine in your code.
_engine = new Microsoft.VisualBasic.Vsa.VsaEngine();
_engine = new Microsoft.JScript.Vsa.VsaEngine();
A unique name called a root moniker is used to identify each scripting engine. The root moniker is in the format “protocol://path” and should be unique to the host. You can use whatever you want as the root moniker, but try naming it something relative to your program. For example, try “yourCompany://yourProgram/language/engineCount”. This will allow easier identification of the engines during debugging and inspection.
_engine.RootMoniker = rootMoniker;
Each scripting engine will need to communicate with an
IVsaSite instance. The scripting engine interface contains a property called “
Site” that needs to be assigned your implementation of the
_engine.Site = this;
After the root moniker and site have been set, you must call the
InitNew method on the engine. This prepares the engine to have new items added to it. Items such as code items, reference items, or global items. It must also occur before you set the root namespace of the engine.
In addition to the root moniker, you will need to specify a root namespace. All types created by the engine will be nested into this root namespace. When the engine runs a script, it will compile the script into an in-memory assembly. You will have access to all of the types created by the script, and the types loaded by the engine. You will need to use the root namespace you specified when the engine was created to gain access to the types you are looking for.
_engine.RootNamespace = rootNamespace;
The first things that need to be addressed after the engine has been created and initialized are any references that may be needed by your scripts. References can be added to the engine by adding items of type
VsaItemType.Reference. Any type that will be used in the script must have a reference supplied to the engine, otherwise your scripts will break. Just like in your program, you cannot use a type if you do not have a reference to its assembly.
You may create as many or as few assembly references as you want, however, without creating references to the Types in use, you will run into errors when the script executes. To create an assembly reference, add an item of type
VsaItemTypes.Reference to the engine with its “
AssemblyName” set to the name of the assembly. For example, to add an assembly reference to the System.dll assembly, the following code would be necessary:
public virtual IVsaReferenceItem
CreateReferenceItem(string itemName, string assemblyName)
Debug.Assert(itemName != null && itemName != string.Empty);
IVsaReferenceItem item =
itemName, VsaItemType.Reference, VsaItemFlag.None);
item.AssemblyName = assemblyName;
Be mindful to add all of the references that your scripts might need. References such as System.dll or Mscorlib.dll are critical to the successful execution of your scripts. No references are added by default for you, you must take it upon yourself to add the needed references. Your scripts will be limited by the references it has access to.
After you have created all of the assembly references, you now need to add the script to be run by the engine. This can be done by adding an item of type
VsaItemTypes.Code and setting its “
SourceText” property to the contents of the script. This should be done before any global items are added to the script. Loading the script from a file is the most extensible means in my opinion, however you are not limited to this means. You could supply the script body from any source available, such as a resource file, or even in your own source code. If you choose to load the script from a file, simply use a
StreamReader or another .NET framework class to read the contents of the file into a
String variable. After the script has been loaded from the file, simply set the “
SourceText” property using the loaded script.
A code item must be added to the engine before you can supply the item with the script to be executed by the engine. The following is an example of how you might add the code item to the engine:
public virtual IVsaCodeItem CreateCodeItem(string itemName)
Debug.Assert(itemName != null && itemName != string.Empty);
IVsaCodeItem item =
itemName, VsaItemType.Code, VsaItemFlag.None);
This is perhaps the most interesting part of hosting your own scripting engine. Many large scale applications allow scripts to modify the objects that are running the program. Using VSA, you can publish your own object instances from your program to be used by the scripts. How cool is that?!
To publish your own object instances and make them available to the script, you will add items of type
VsaItemType.AppGlobal to the engine. As you add these global items to the engine, the name that you supply the item will be the name of the variable, or object instance, in the script. So take care when naming these items as it will affect their usage in the script. Take for example, you want to publish a form instance to a script. If you name the global item “mainForm”, then it will be accessible in the script as a global variable named “
mainForm”. You may then call methods upon the variable just like you would in your program. To close the form, you might use
mainForm.Close(); if you were using JScript.
After you add the code item, it is necessary to set the item’s “
TypeString” property to the fully qualified type name of the object you are publishing. So again, if you were publishing a
Form object, you might use the following code to set the item’s
Here's some sample code that demonstrates how you might publish one of your object instances to be available to script:
IVsaGlobalItem item =
"mainForm", VsaItemType.AppGlobal, VsaItemFlag.None);
string typeName = typeof(System.Windows.Forms.Form).FullName;
item.TypeString = typeName;
Now, you might be wondering how the script is going to gain access to the actual instance you want to use? No where did you specify any variable instance yet to be used for this “
mainForm” variable. The key is in the fact that the scripting engine communicates back with your program using the
IVsaSite interface. Whenever the engine encounters an item of type
VsaItemType.AppGlobal, it will call back on the
IVsaSite interface to retrieve the object instance for the item.
At first, this seems a bit odd, but it does have its advantages. Because you are not required to pass the object instance to the engine when the global item is created, you can defer the object creation until the engine calls for it. This may not be useful, but under the right conditions, it could prove extremely useful to keep a small memory footprint and reduce resource consumption. This does present a problem for the implementer of the
IVsaSite interface. How can you return the instance of the object when then engine calls for it, since it will ask for it using the name you used when you added the item?
This can be a simple case of using a
Hashtable to store the object instances by their item name when they are added to the engine as a global item. You could create some other lookup or creation mechanism, but for the purposes of simplicity, we will assume that you will store the object instance in a
Hashtable when you add a global item to the engine. Something like this…
Now that you have a way to lookup the instance by its item name, all you have to do is wait for the engine to call you back and return the object when it asks for it. Your specific implementation of the
IVsaSite interface will dictate exactly how this lookup process will work. Only trial and error combined with careful design will lead you to the most useful implementation for your needs. Using a lookup table is pretty basic and not all that complex, so it’s easy to setup and debug. Here’s how this lookup appears in my example project. This code can be found in my
public object GetGlobalInstance(string name)
foreach(DictionaryEntry entry in _globalItemLookupTable)
if (string.Compare(entry.Key.ToString(), name, true) == 0)
Just because you can publish objects for the scripts to program against, doesn’t mean that you should. The more objects that you expose, the more resources you will use, and the slower things will get. Just keep that in mind as a rule of thumb. Try and keep this in mind as you publish objects for use in the scripts. Try to only publish objects that you know will be useful in the scripts. Only you can determine what is or is not useful for your scripts.
Publishing your own object model in this fashion allows the scripts to easily modify your existing program. Things like changing menu items or responding to program events, suddenly become trivial script code. Publishing a well thought out object model for the scripts to program against, effectively supplies the hooks needed to extend your program in the future.
There isn’t much left at this point. You’ve added your assembly references, you’ve loaded the script and added the code item, and finally, you’ve added your own objects so that the script can program against them. All you need to do at this point is compile the script and run it! You can accomplish this easily by calling the following methods upon the scripting engine:
The scripting engine will call you back using the
IVsaSite interface through the
OnCompilerError() method. Any errors encountered during compilation, or during script execution, will be captured and directed to this method. Depending upon the error, you will need to take the appropriate actions to handle the error. It’s been my experience that once you get an error, there’s not much you can do but correct the script and try again. Make sure you use decent amounts of structured exception handling in your script files to prevent errors from reaching this method.
So in the interest of wrapping things up, and getting you on to running your own scripts using VSA, let’s review some of the major points we’ve covered. Visual Studio for Applications, or VSA, replaces the older technology known previously as VBA, or Visual Basic for Applications. VSA is not limited to only VBScript, any .NET scripting language can be used. You may have to create a custom engine for scripting languages if you are trying to use some language other than VBScript or Jscript.
Microsoft has supplied us with two pre-built engines that can be used just for these languages. You will need to install the .NET Framework, and add references to the Microsoft.Vsa.dll and the assembly containing the engine of your choice. The aforementioned engines can be found in these assemblies. The VBScript engine is located in the Microsoft.VisualBasic.Vsa.dll, and the JScript engine is located in the Microsoft.Jscript.dll.
You must first create and then initialize your scripting engine before you can add items to it. Items define assembly references, the script to be executed, and any objects that you want to publish to the script. You will need to implement the
IVsaSite interface to enable communication between the script engine and your program. Without this interface, you will not be notified of errors in the scripts, nor can the engine request instances for the published objects when it requires them.
The scripting engines are capable of compiling scripts, and then storing that compiled IL for later use. This can improve performance, but it’s not as extensible as compiling the scripts when your program runs. I would recommend just loading the scripts from a file at runtime. This allows anyone to modify the scripts and make changes to them using a simple text editor. Much easier to modify text than precompiled Intermediate Language.
I hope this article has helped you to understand the benefits of VSA, and helped you to understand how you might implement it in your own programs. I have written a small example and assembly that you can download and run/debug at your leisure. It features a scripting host that can run either VBScript or JScript code files. I tended to dislike VBScript so I focused on JScript, and so you will find more JScript examples than VBScript examples. The examples show in a very simple application how to publish objects and modify the program using the scripts. The scripts also show how to respond to events inside the program, directly in the script files!
The demo application simply creates a simple form object and publishes it to the scripts. The scripts modify the form by adding some menu items, and wiring up to some of the form's events.
I've created some wrapper classes to help make creating a scripting host and running script files trivial. There are two main classes with which you will work with:
VsaScriptingHost class exposes methods and events to help you load your scripts. Add objects and assembly references to the scripting host. This class also helps you compile safely and catch exceptions by means of an event. The events you'll need to work with are as follows:
public event ScriptingHostEventHandler AssemblyReferencesNeeded;
public event ScriptingHostEventHandler GlobalItemsNeeded;
public event ScriptingHostCompilerExceptionEventHandler CompilerException;
In addition to the
VsaScriptingHost class, I also created the
VsaScriptingHostFactory which helps to create instances of the
VsaScriptingHost class. The factory class is capable of determining the type of script engine to use based upon a script file. In other words, JScript (*.js) script files would result in a scripting host instance that is capable of running JScript. This is all quite painless with the factory hiding most of the grunt work and details from you. From the sample code, this is how you might combine these classes to work for you:
VsaScriptingHost hosts = _factory.Create(@"MyScriptingHost",
@"Scripting", true, Application.StartupPath);
foreach(VsaScriptingHost host in hosts)
host.CompilerException += new
host.GlobalItemsNeeded += new ScriptingHostEventHandler(OnGlobalItemsNeeded);
foreach(VsaScriptingHost host in hosts)
Using those events as triggers to setup your scripts and apps to be extended by the scripts is also quite trivial. From the sample, here's how I exposed the form instance to the scripts. Also note that I added the starting assembly (*.exe) as a reference to the host, because the form class is found in that assembly. Do not forget to add your assembly references!!!
private static void OnAssemblyReferencesNeeded(object sender,
private static void OnCompilerException(object sender,
private static void OnGlobalItemsNeeded(object sender,
e.Host.CreateGlobalItem("_scriptableForm", _scriptableForm, false);
Well, that's about it. Take a look at the source to the host and the hosting factory if you are wondering about the gritty details. I hope this helps you understand how you can add scripting capabilities to your .NET applications.
- February 2005 - Initial release.