VSA Scripting in .NET

Using the Engine

How do I add assembly references?

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.

Add an item to create an Assembly Reference

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:

/// <summary>
/// Creates an IVsaReferenceItem item using the current IVsaEngine
/// </summary>
/// <param name="itemName"></param>
/// <returns></returns>
public virtual IVsaReferenceItem
      CreateReferenceItem(string itemName, string assemblyName)
{
    Debug.Assert(itemName != null && itemName != string.Empty);
    // assert the item is unique 
    this.AssertEngineItemUnique(itemName);

    // create a new reference item
    IVsaReferenceItem item =
        (IVsaReferenceItem)_engine.Items.CreateItem(
            itemName, VsaItemType.Reference, VsaItemFlag.None);
    // set the item's assembly name
    item.AssemblyName = assemblyName;
    return item;
}

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.

How do I load the script into the engine?

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 StringBuilder or String variable. After the script has been loaded from the file, simply set the “ SourceText ” property using the loaded script.

Adding the code item that will contain the 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:

/// <summary>
/// Creates an IVsaCodeItem using the current IVsaEngine
/// </summary>
/// <param name="itemName"></param>
/// <returns></returns>
public virtual IVsaCodeItem CreateCodeItem(string itemName)
{
    Debug.Assert(itemName != null && itemName != string.Empty);

    // assert the item is unique
    this.AssertEngineItemUnique(itemName);
   
    // add a code item to the engine
    IVsaCodeItem item =
        (IVsaCodeItem)_engine.Items.CreateItem(
            itemName, VsaItemType.Code, VsaItemFlag.None);   
    return item;
}

How do I publish my own objects for use in the scripts?

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?!

Adding the global item that will be used to publish your own object instance

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 TypeString .

Here's some sample code that demonstrates how you might publish one of your object instances to be available to script:

// create a new global item
IVsaGlobalItem item =
    (IVsaGlobalItem)_engine.Items.CreateItem(
        “mainForm”, VsaItemType.AppGlobal, VsaItemFlag.None);

string typeName = typeof(System.Windows.Forms.Form).FullName;     

// set the item's type string to the instance's type's fullname
item.TypeString = typeName;

How does the engine find the object instance for a published object?

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…

// cash item in globalItemTable
_globalItemLookupTable.Add(itemName, instance);

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 VsaScriptingHost class.

/// <summary>
/// When the engine calls back the IVsaSite to ask for a global item,
/// return the instance if we've cached it previously
/// </summary>
/// <param name="name">The name of the global item
/// to which an object instance is requested</param>
/// <returns></returns>
public object GetGlobalInstance(string name)
{
    // enumerate the items in our global item lookup table
        foreach(DictionaryEntry entry in _globalItemLookupTable)

        // carefully comparing the item names
        // to the name of the item requested by the engine
        if (string.Compare(entry.Key.ToString(), name, true) == 0)
            // and if we find it, return the object instance to the engine
                return entry.Value;

    // and finally if we couldn't find any instance
    // for that name, just don't worry about it
    return null;
}

You might also like...

Comments

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“My definition of an expert in any field is a person who knows enough about what's really going on to be scared.” - P. J. Plauger