Adding ActiveScript plugin functionality to your MFC Applications.

Give your applications user-defined actions. Easy. Painless. Quick.

Introduction & Background:
Possibly one of the most requested feature for programmers get is to allow the end user to customize or extend the program to their needs. This is most common with programs that do manipulations on data, such as text, html, graphics, or data transactions. Adobe’s Photoshop™ program gave us the concept of “plugin” to our lexicon, which has been successfully applied to various applications.
The usual plugin design for most programs is modeled after the Photoshop method: a DLL (either with a special filename or in a special folder), containing very specific function exports. This is a good and fine way of doing it, but it has some limitations: it usually requires a costly (or complicated and poorly documented) development environment to create compatible DLLs; and it can be a pain to develop the interface for the DLLs, as anyone who has tried to make a class library in C++ work under Delphi or vice-versa can tell you.

For some time, Microsoft has provided a macro language in Word that could be used to do things such as mail merging and printing, formatting for special layouts, and so on. Perhaps one macro that most developers have run across at one time or another is the help authoring set, which lets your make .HLP files. Both RoboHelp and Doc2Help use macros like this (although much more advanced) to manage help authoring, and have extended them in recent years to give complete documentation from a single source (printed, electronic, web, .HLP). A few years ago, Microsoft changed the macro language from one that was specific to Word to a variation of Visual Basic, called Visual Basic for Applications. This is the same macro language that is in Visual Studio, along with all Microsoft Office programs and Outlook. The advantage of this using this common language, across all products, is quite obvious.

About the same time, Netscape took the macro language idea to the web by using JavaScript – a “lite” version of Java, with very superficial similarities - in their web browser. Microsoft of course followed suit, and added both JavaScript and VBScript, which is an even more stripped-down version of Visual Basic for Applications. Now, what makes the Microsoft implementation of scripting for the Internet Explorer browser interesting to us is that they are COM objects, and Microsoft has published the interface for using them, under the heading of ActiveScript, and more recently, Windows Script Hosting. There are at least two additional scripting languages that have been ported to run under this: PerlScript (www.activestate.com) and PythonScript (www.python.org). By using Windows Scripting Host, one can make a “better batch language”, and several developers use this to automate their builds. We actually use WSH to automate the release build, copy the files to the server, build the install image, and prep the CD burner program – all we do is load the blank and press the button. Working hard or hardly working?
Using ActiveScript:
In general terms, using the scripting language means that your program becomes a Script Host, capable of responding to and firing events, exposing programmable objects, and the like. You do this by implementing IActiveScriptSite in your program, and creating an instance of IActiveScriptParse, which is then tied to the correct script language interpreter (note that you cannot just give it a script without the language being know ahead of time). There are more features in hosting scripts than you will probably use, and we will not explore the abilities to sink and source events, just on exposing programmable objects and calling functions within those scripts; this is more than powerful enough for most applications. More advanced readers might be interested in dealing with events, and the possibility of putting your own script language within your program to be used from the same interface (this may be handy for your application if the existing JavaScript and VBScript does not offer enough functionality for you, or if you wish to embed Perl into you application). If you are familiar with COM and ATL programming, you already know enough on how to sink and source events, and adding this functionality will be simple for you.
System Requirements:
For ActiveScript to be usable, the system must have the appropriate dlls and COM objects installed, and at least Internet Explorer 3.0 installed – not a major requirement. These can be downloaded from Microsoft’s MSDN site, at http://www.microsoft.com/msdownload/vbscript/scripting.asp or directly from http://www.microsoft.com/scripting/downloads/ws/x86/ste51en.exe. The web page for WSH in general is at http://msdn.microsoft.com/scripting/default.htm. We also have made it available here, in the Files section below. The DLLs required are very small, and will add about 740k to your install package overhead.
Performance:
In testing, we ported half-a-dozen real, live, and in current use, Java classes and Visual Basic COM objects over to their corresponding JavaScript and VBScript counterparts. After adding the support to the application that was using them, we found that the scripted versions were as fast or faster than the pre-compiled versions. This is especially true with the Java classes, as it took at least 10 seconds for the Java class to be loaded in, and about less than a quarter second for the JavaScript translation to load in (this was on a 200mhz Pentium under NT4sp5). The largest script/class source was 30k. The Windows Script Host install from Microsoft is only 740k in size, versus about 5 megs for the Java system. For those that are concerned about people swiping your script code (which is just a text file), Microsoft offers an encrypting solution, similar to the one the can be used with Perl modules.
Explanation of the classes and using them:
The ActiveScript classes that we have in the Standalone Demo project are based on a much larger project, but cleaned up considerably and stripped of all the specific material that was required. The end result is a great starting point to putting your own scripting in, with almost zero work on your part.

CActiveScriptSite – this is the main class you will be using, and where you “host” the script from; all of your scripts are bound to this class. For each new script loaded in, you will need to create a new CActiveScriptSite object and use it. This class contains two additional classes that handle adding new script functions and overriding existing ones:
CActiveScript_Functions – this class exposes global functions for you to use. We have provided some basic functions for you, but you can add you own. You can also use this class as a model to make your own script object that gives functions out, if you wish to namespace scope the functions.
CActiveScript_MsgBox – this is a class that is used to prevent the VBScript MsgBox function from popping up a dialog, thus halting the application. Instead it writes the data out to a log file – you will need to add your own logging code in if you want to capture it.
To use the classes, you will simply need to create a CActiveScriptSite object (create via new, as a member element, or derive from it), and call the LoadScript function. This function takes 2 parameters, and handles creating and loading of the needed class objects:
OLE String pointer to the language string (usually JavaScript or VBScript);
OLE String pointer to the script’s text – loaded from a file into memory.
If LoadScript returns false, something went wrong – usually the script itself is bad, and you will get messages via the OnScriptError function (a virtual function from the base class of IActiveScriptSite). Otherwise, it will return true with the IDispatch pointer set.
Once the script is loaded, you can keep it around for as long as your program is running; if you need to re-load the script (perhaps it has changed, or else you want to refresh the globals it is using), simply delete the CActiveScriptSite object and do it over.
To call a function in the script, you will need to get the DISPID of the function, and call it with the appropriate number of parms. We have provided a trivial function called GetIdOfFunction that gets the id of the function in the script loaded.
If you are defining a standard interface for your program – for example, each script simply must have a “DoCoolThing” function that takes three long values - then you can simply code it that way. Otherwise, you will have to be creative and deal with whatever interface you define.
In looking at other products that use ActiveScript, there seems to be two general methods that are used: one is to have a global named object added in LoadScript that exports your named space (this is what Internet Explorer and Netscape do with JavaScript, and Windows Script Host does with VBScript) that gives the functions out, and each specially named function (onlogin(), onlogout(), etc) is called. Another is to pass this object in as a VT_DISPATCH type. Either method will work fine. The demo code uses forms of both methods to show how it’s done, by deriving CDemoScriptSite from CActiveScriptSite. The method that you will use depends on your program’s needs.
Putting ActiveScript into your MFC applications:
If you are starting out with a new project, use the New Application wizard and make sure that the Automation checkbox is set – this will create a project with the CDocument class having dispatch methods. Otherwise, use Insert->New Class, and make a new MFC class from CCmdTarget, and select the Automation radio button.
Either method will give you a class that has the ability to expose functions and properties to other COM objects. With the AppWizard-generated project, you will get a .ODL file that makes type libraries; this ODL file is used only by ClassView (not ClassWizard) to show a treeview of the objects and functions; ClassWizard uses the actual source code and does the same thing.
For any automation class that you wish to allow our ActiveScript Framework to use, you will need to add two functions:
void  AddNamedItems(IActiveScript *pAS);
BOOL  GetItemInfo(LPCOLESTR pstrName,IUnknown **ppiunkItem);

These functions add the name of the object to the script’s namespace, and resolve the name when the script needs to use it. In the  AddNamedItems function, add your names like this:
pAS->AddNamedItem(oleStrName,flags);

You will need to pass in the object name as an OLE string – the simplest method is to use OLESTR("objectName"). The values for flags are defined within MSDN under AddNamedItem, but you will generally use SCRIPTITEM_ISVISIBLE, and add in SCRIPTITEM_GLOBALMEMBERS when you wish to add new low-level function names to the script (like “printf” for example).
GetItemInfo is used to resolve the object names put in via AddNamedItem. If this matches one, it should set the IUknown value and return TRUE. The usual way to do this is like this:
if (wcscmp(pstrName, OLESTR("objectName")) == 0)
{
   *ppiunkItem = GetIDispatch(TRUE);
   return TRUE;
}
return FALSE;

This assumes that the class in question was derived from CCmdTarget or a derived object.

Just adding these two functions to your automation-enabled class is not enough; unless the ActiveScript handler calls your class, nothing happens! Depending on your application, you can solve this in several different ways. In the CActiveScriptSite, there is a virtual function called AddNamedItems, which in turn calls the AddNamedItems function in the automation classes it handles. You can either modify this class to suit your needs, or else derive a class from it and extend this function.

In the Demo source, we derive from CActiveScriptSite and add handlers for the Document class (“DemoDoc”) and a separate automation class called CWayCool. CWayCool is passed as a function parameter to some sample scripts, so that you can see how it is possible to access the item. CWayCool is not added to the namespace, but is instead passed via a VT_DISPATCH parm at runtime. DemoDoc is added a global object, so that it can be used in any place in the script.

To call a script function, you must first get the dispatch pointer for it. This is done by using the GetIdOfFunction method in the CActiveScriptSite class. If it returns TRUE, then you can call that function. Notice that you can get exceptions thrown here from MFC, so you will probably want to wrapper things:
try
{
   DISPID dispid;
   if (scr.GetIdOfFunction(OLESTR("fname"),&dispid))
   {
      long result=0;
      scr.InvokeHelper(dispid,DISPATCH_METHOD,
                       VT_EMPTY, (void*)&result, NULL);
   }
}
catch (COleException *e)
{
   SCODE sc=e->m_sc;
}

The above example takes no input and has no output – we include the long result part for clarity, even though it is not used. For taking output, you would change the VT_EMPTY value to something like VT_I4, which is a return value of long. For providing input, you give it the usual COM VT_xyz array of the items to push into it.

The demo application shows the very basics of how to put ActiveScripting into your applications in the CMainFrame::OnRun1 through OnRun4 functions. These functions show the basics of calling the global class object DemoDoc from inside of the script, on calling a function with parms and return values, using the passed-in COM parm object, and what errors look like when you get them (#4 will always error out and pop a dialog in debug builds).
Advantages of using ActiveScript:
We have found several advantages of using ActiveScript vs. other methods for adding plugin capabilities to our programs:
The disadvantages of using ActiveScript:

Downloadables:
Current ActiveScript Framework and Demo (4-11-2000): ActiveScriptDemo.zip - 31k in size.
Includes the source code for the framework (ActiveScriptSite.h, ActiveScriptFunctions.h & .cpp) and the demo showing some simple and crude examples. The script samples are all in the code as strings, not seperate files.

Windows Script Version 5.1 - 739k in size. This is the installer from Microsoft and adds the current JavaScript and VBScript engines to the system. Requires at least Internet Explorer 4.0 to be installed.


Updated: Thursday, Sep 18, 2008
Contents copyright © 1999-2003 by NOPcode.com and its subsidiaries. Reproduction in part or in whole prohibited unless explicitly granted. All information and products, be it documentation, essays, source code, programs, or installs, are provided "as-is" and neither the author nor NOPcode.com will be held responsible for any resulting damages, either physical or mental. No warranty is expressed nor implied. All rights reserved. All trademarks used are property of their respected companies. For further information contact .