SAMPLE: MFCAxs.exe Implements an Active Script Host Using MFC

ID: Q168214


The information in this article applies to:


SUMMARY

MFCAxs.exe is a sample that contains MfcAxscrVb. MfcAxscrVb is an instructional sample Active Scripting host written using MFC. It hosts the VBScript DLL; however, the Active Scripting mechanisms are generic to any Active Scripting engine. MfcAxscrVb shows just one possible way to provide dispatch objects from the host, to implement the host's Active Scripting interfaces, and to connect the messages of windows in the host with events fired to the scripting engine.

MfcAxscrVb includes a Visual C++ 5.0 workspace and project file. Although no VC++ 4.2 compatible makefile or mdpfile is provided, the source code for MfcAxscrVb is compatible with MFC 4.2b.

Prerequisites: COM, Automation


MORE INFORMATION

The following file is available for download from the Microsoft Software Library:

MFCAxs.exe
Release Date:

For more information about downloading files from the Microsoft Software Library, please see the following article in the Microsoft Knowledge Base:
Q119591 How to Obtain Microsoft Support Files from Online Services

Running the Sample

When you run the MfcAxscrVb sample you will notice a large edit control in the upper left-hand corner of the application. You can paste or type script into this window and select the "Run Script" button to invoke the script. Several test scripts are provided for you in the Script.txt and Testevents.txt files.

Exposing the Host: Dispatch Objects

The main focus for this sample is the implementation of dispatch objects using MFC. The reason for this is simple. A scripting language isn't much use if there isn't anything to script.

The core technology upon which all Active Scripting depends is most certainly that of Automation. A solid working knowledge of Automation is absolutely necessary before attempting to code any Active Scripting host. There are several good references for Automation: chapters 14 and 15 of Brockshmidt's "Inside OLE," chapter 11 of Dale Rogerson's "Inside COM," and the "OLE Automation Programmer's Reference."

CCmdTargetPlus:

What you need are some objects that allow for scriptable properties and methods through IDispatch but also generate scriptable events. MFC definitely does most of the grunt work for you when implementing dispatch interfaces for a COM object. However, the only MFC class that supports both incoming dispatch interfaces and outgoing dispatch interfaces, or event sinks, is COleControl. COleControl is much too bulky for what you want. You just want events, a dispatch interface, and type information. VBScript needs to read type information for dispatch objects that support events. You could either create this on the fly, or create a static type library for your host's dispatch objects and obtain the ITypeInfo for each individual object.

While probably not necessary, MfcAxscrVb has a base class called CCmdTargetPlus that supports these three pieces: dispatch interface, connection points with a control-style event map, and easily accessible type information through IProvideClassInfo. Most of the code in CmdTargetPlus was added to allow for event maps, which makes adding events to derived classes much easier.

Dispatch Objects:

All host-provided dispatch objects are implemented using CCmdTargetPlus. There are a few things they need to do to work well. Refer to any of the dispatch objects (CAButtonDispatch, CBButtonDispatch, and so forth) for a boilerplate on what is necessary. A brief rundown on MfcAxscrvb's object model is listed at the end of this document.

If you add your own objects, be sure to double-check for three unique GUIDs for the primary dispatch and primary events interfaces, and for the clsid for the object in sum. This needs to be done in the implementation file and in the .odl file. (Cut and Paste helps here.) Cut and paste all of the class wizard macros, and then change them to the new class name. Class Wizard is very particular, but if you follow everything just right in the .odl, .h, and .cpp files, you can use it to add new events, properties, and methods to your dispatch object as if it were an MFC OLE Control. This is sort of handy.

MfcAxscrVb keeps the object implementing the dispatch mechanisms separate from the MFC object, which it is actually referring to. For example, the dialog box has a button called the "AButton," which is scriptable. The MFC button object is CButton. The CCmdTargetPlus-derived class, CAButtonDispatch, is separate and distinct. This is a key point. Only what the host programmer explicitly chooses to expose for basic MFC objects and Windows controls will be scriptable.

CEventsButton, CEventsEdit, CeventsListBox:

For events, there needs to be some code that responds to a Windows event-- say, a button click--and generates an event for the script engine. (Remember an event is a dispatch Invoke on an interface handled by connection points in the host object.) This sample accomplishes this by adding standard MFC message handlers into objects derived from the various classes. Each of these handlers merely has to call FireEvent (courtesy of the code added to CCmdTargetPlus) on the dispatch object. Everything else happens automatically.

Because the dispatch object and the actual MFC window object are separate in this sample, the dialog parent needs to explicitly hook the two together. This is done in the Mfcaxscrvbdlg constructor.

Named Items

Once you have dispatch objects, you have to set them up so that the script engine knows about them. The collection of all the named dispatch objects that the script engine knows about is called the "Script Namespace." Items are added to the script namespace through the IActiveScript::AddNamedItem method. As I mentioned previously, it is the host's duty to implement an IDispatch for the object and support type-information through ITypeInfo for the object. When the script engine needs to resolve a reference to a named item, it uses the IActiveScriptSite::GetItemInfo method to request an IUnknown pointer (which it queries mainly for IDispatch) and an ITypeInfo pointer.

Not everything that a host wants to be scriptable needs to be a named item. Usually the host will have a hierarchy of objects, where sub-objects are accessible through a higher-level object. A common metaphor is the Application->Document->Item hierarchy, where the Document object is accessible from the top-level Application object as an IDispatch property of the Application object itself. In the same manner, the Document object itself exposes several sub-objects and each is an Item that you can obtain through an Item array or other contrivance.

Using this scheme, the scripting engine is smart enough to navigate to a sub-item when the script code says the following without needing Document or Item to be added to the script namespace:

Set Obj = Application.Document.Item(1) 
As long as the sub-objects are exposed as get-properties of their parent object, the script engine finds them successfully. Interestingly enough, the script engine is able to "assume" the top-level item in the hierarchy. What this means is that if Application is declared as the top-level item, then it is sufficient to say the following for the prior script code:

Set Obj = Document.Item(1) 
This is exactly how Internet Explorer 3.0 allows you to script code in the context of the Window object without always prefixing all references to objects with "Window." A script host needs to identify the top-level script item in the call to AddNamedItems by using the SCRIPTITEM_GLOBALMEMBERS flag. What usually isn't made clear by Active Scripting documentation, however, is that only named items can support events. Using the above example, there is no syntax to say the following to handle an event in a particular item:

   Sub Application.Document.Item(1)_OnEvent ' this doesn't work! 
So, for MfcAxscrvbdlg, because we want to be able to handle the events of every object in the hierarchy, every object is added as a Named Item. Each object is also accessible through the top-level Scripter object (the dialog itself), which does not support events currently.

WebBrowser Control:

In this sample, the script host exposes only one external object to the script namespace. This is the WebBrowser control hosted on the dialog by MFC's default control containment support. All you need is an AddRef'd dispatch pointer to the control, and you're set. For events, the external object needs to expose an ITypeInfo pointer somehow. MfcAxscrVb gets this through IProvideClassInfo::GetClassInfo. As a last resort, a host could read the object's type-information itself to expose an ITypeInfo. Fortunately, the WebBrowser control supports GetClassInfo, so MfcAxscrVb doesn't do this.

If you wanted to add other external automation objects to the namespace, all that usually is necessary is to obtain an IUnknown interface using CoCreateInstance. Alternatively, the host can support a generic means for obtaining external objects, albeit ones that don't need to have events handled. In Visual Basic, the function to do this is called CreateObject.

Getting to Other Apps: HostCreateObject

Other than the set of properties used to expose all of the child objects, the main Scripter object (the dialog class itself) exposes only one special method. This method demonstrates a simple means for simulating VB's CreateObject function. Note that the method is named HostCreateObject to emphasize that this method is not provided for free by the script engine, but the host must implement it itself. Some hosts clearly would not want to provide this ability for security reasons, IE3 is a prime example. The primary goal of HostCreateObject is to return the dispatch pointer of the requested object. Once the scripting engine has the dispatch pointer, it knows what to do from there.

NOTE: As of version 2.0, the VBScript and JScript engines now support CreateObject as a built-in function. The host does not need to implement this function. This function will, however, test an object to make sure it is safe for scripting before allowing the object to be used. Objects deem themselves safe for scripting by either supporting the IObjectSafety interface or marking the appropriate Component Category registry entries for Safe-For-Scripting. Refer to the Microsoft Knowledge Base for more information.

Class Wizard Support

The last little bit of trickery in MfcAxscrVb is the manipulations made to support ClassWizard. Class Wizard, technically isn't "dumb." It is just mentally challenged. It is pretty convenient for what it does, but remember that it just blindly searches files looking for those special ClassWizard comments. If you follow the format for COleControl, you can just about get away with using ClassWizard's Automation and Events tabs.

One complication is that MfcAxscrVb uses derivation in some of its object hierarchy. All of the button objects are derived from a common CButtonDispatch object that exposes properties and methods, all of which it would individually support. However, when it comes time to fill out the .odl file, each individual object needs to contain all of the properties and methods of the parent class. Needless to say, this is going to confuse Class Wizard when it comes time to number the DISPIDs. If you use Class Wizard, make sure you double-check the generated DISPIDs in two places: the .odl file for the automation class and the enumeration inside the class definition. When working with the derived button classes, it is easy to see the same DISPID assigned to multiple properties and methods. It is much easier to correct the DISPIDs, though, than to add everything by scratch, so the functionality has been left in MfcAxscrVb.

With Visual C++ 5.0's improved support of IDL methods and properties through ClassView, it's a toss up whether ClassWizard is needed or not. But MfcAxscrVb supports it for now. Enjoy.

Appendix A: Object Model

Scripter:

This is the master object from which all other objects are derived. It is "IMfcaxscrvbDlg" in the .odl file but is added as the Named Item "Scripter." It has one method, HostCreateObject, which is described above.

Button Objects:

AButton BButton, CancelButton, OKButton, RunScript

Properties Methods Press: Acts as if the button were pushed. Events BroCon:

BroCon is the name of the WebBrowser control on the dialog box. It supports all properties and methods as documented in the Internet Client SDK/ActiveX SDK for the WebBrowser control.

EditCon:

Methods Events Lbox:

Methods Events

Additional query words:


Keywords          : kbfile kbole kbsample kbIE301 kbIE400 kbIE401 kbMFC kbVBScript100 kbVBScript200 kbVC500 kbIE302 
Version           : WINDOWS:1.1,2.0,3.01,3.02,4.0,4.01; winnt:5.0
Platform          : WINDOWS winnt 
Issue type        : 

Last Reviewed: April 6, 1999