How to add support for hosing VBScript to your MFC application (221992)



The information in this article applies to:

  • The Microsoft Foundation Classes (MFC), when used with:
    • Microsoft Visual C++, 32-bit Enterprise Edition 5.0
    • Microsoft Visual C++, 32-bit Enterprise Edition 6.0
    • Microsoft Visual C++, 32-bit Professional Edition 5.0
    • Microsoft Visual C++, 32-bit Professional Edition 6.0
    • Microsoft Visual C++, 32-bit Learning Edition 6.0

This article was previously published under Q221992

SUMMARY

You can add VBA-like scripting capability to your MFC application with little overhead that uses Microsoft ActiveX Scripting technologies. This article demonstrates how to create a new MFC application, or modify an existing one, that incorporates support for VBScript.

MORE INFORMATION

Follow the steps below to build and run the example:
  1. Create a new MFC dialog-based application, or use an application you already have to which you would like to add scripting support.
  2. Add an edit box and button to your dialog, and enable the "Want return" and "Multiline" styles in the properties for the edit box.
  3. Use ClassWizard (CTRL-W), under the Member Variables tab, to associate IDC_EDIT1 with a member variable of type CEdit named m_edit1.
  4. Use ClassWizard to create a new class called MyScriptObject, derived from CCmdTarget, with support for Automation. Click OK if you see a warning about an ODL file.
  5. Under the Automation tab in ClassWizard, select MyScriptObject, and add the following methods:

    long gcd(long a, long b);
    void HelpAbout();
    void ShowValue(LPCTSTR prompt, long n);
  6. Implement gcd, HelpAbout, and ShowValue as follows:
       long MyScriptObject::gcd(long a, long b) 
       {
          int l, h, t;
          if(a < b) {
             l = a;
             h = b;
          }
          else if(a > b) {
             l = b;
             h = a;
          }
          else return a;
    
          while(h%l != 0) {
             t = l;
             l = (h%l);
             h = t;
          }
          return l;
       }
    
       void MyScriptObject::HelpAbout() 
       {
          AfxMessageBox("HelpAbout: My Script Object!", 0x10000);
       }
    
       void MyScriptObject::ShowValue(LPCTSTR prompt, long n) 
       {
          CString str;
          str.Format("%s%d", prompt, n);
          AfxMessageBox(str, MB_SETFOREGROUND);
       }
    					
  7. Open MyScriptObject.h, and move the virtual destructor ~MyScriptObject from the protected section of the class to a public section.
  8. Double-click the dialog button to create a handler function for it, and implement it as follows:
       // Initialize our IActiveScriptSite implementation with your
       // script object's IUnknown interface...
       g_iActiveScriptSite.m_pUnkScriptObject = 
          m_myScriptObject.GetInterface(&IID_IUnknown);
    
       // Start inproc script engine, VBSCRIPT.DLL
       HRVERIFY(CoCreateInstance(CLSID_VBScript, NULL, CLSCTX_INPROC_SERVER, 
          IID_IActiveScript, (void **)&m_iActiveScript), 
          "CoCreateInstance() for CLSID_VBScript");
    
       // Get engine's IActiveScriptParse interface.
       HRVERIFY(m_iActiveScript->QueryInterface(IID_IActiveScriptParse, 
          (void **)&m_iActiveScriptParse), 
          "QueryInterface() for IID_IActiveScriptParse");
    
       // Give engine our IActiveScriptSite interface...
       HRVERIFY(m_iActiveScript->SetScriptSite(&g_iActiveScriptSite), 
          "IActiveScript::SetScriptSite()");
    
       // Give the engine a chance to initialize itself...
       HRVERIFY(m_iActiveScriptParse->InitNew(),
          "IActiveScriptParse::InitNew()");
    
       // Add a root-level item to the engine's name space...
       HRVERIFY(m_iActiveScript->AddNamedItem(L"MyObject", 
          SCRIPTITEM_ISVISIBLE | SCRIPTITEM_ISSOURCE),
          "IActiveScript::AddNamedItem()");	
    
       // Get script code...
       CString csScriptText;
       m_edit1.GetWindowText(csScriptText);
    
       // Parse the code scriptlet...
       EXCEPINFO ei;
       BSTR pParseText = csScriptText.AllocSysString();
       m_iActiveScriptParse->ParseScriptText(pParseText, L"MyObject", NULL,
          NULL, 0, 0, 0L, NULL, &ei);
    
       // Set the engine state. This line actually triggers the execution
       // of the script.
       m_iActiveScript->SetScriptState(SCRIPTSTATE_CONNECTED);
    
       // Release engine...
       m_iActiveScriptParse->Release();
       m_iActiveScript->Release();
    					
  9. Open your [projectname]Dlg.h file and add the following member variables to a public section of your class:
       MyScriptObject m_myScriptObject;
       IActiveScript *m_iActiveScript;
       IActiveScriptParse *m_iActiveScriptParse;
    					
  10. Also in [projectname]Dlg.h, add the following #include statements, just before the declaration of your class:
       // Include ActiveX Script definitions...
       #include <activscp.h>
       // Include definition for MyScriptObject...
       #include "MyScriptObject.h"
    					
  11. Add the following code to your [projectname]Dlg.cpp file, just before the implementation of your button handler:
    // Your IActiveScriptSite implementation...
    class MyActiveScriptSite : public IActiveScriptSite {
    private:
       ULONG m_dwRef; // Reference count
    public:
       IUnknown *m_pUnkScriptObject; // Pointer to your object that is exposed
                                     // to the script engine in GetItemInfo().
       
       MyActiveScriptSite::MyActiveScriptSite() {m_dwRef = 1;}
       MyActiveScriptSite::~MyActiveScriptSite() {}
       
       // IUnknown methods...
       virtual HRESULT _stdcall QueryInterface(REFIID riid, void **ppvObject) {
          *ppvObject = NULL;
          return E_NOTIMPL;
       }
       virtual ULONG _stdcall AddRef(void) {
          return ++m_dwRef;
       }
       virtual ULONG _stdcall Release(void) {
          if(--m_dwRef == 0) return 0;
          return m_dwRef;
       }
       
       // IActiveScriptSite methods...
       virtual HRESULT _stdcall GetLCID(LCID *plcid) {
          return S_OK;
       }
       
       virtual HRESULT _stdcall GetItemInfo(LPCOLESTR pstrName,
          DWORD dwReturnMask, IUnknown **ppunkItem, ITypeInfo **ppti) {
    
          // Is it expecting an ITypeInfo?
          if(ppti) {
             // Default to NULL.
             *ppti = NULL;
             
             // Return if asking about ITypeInfo... 
             if(dwReturnMask & SCRIPTINFO_ITYPEINFO)
                return TYPE_E_ELEMENTNOTFOUND;
          }
          
          // Is the engine passing an IUnknown buffer?
          if(ppunkItem) {
             // Default to NULL.
             *ppunkItem = NULL;
             
             // Is Script Engine looking for an IUnknown for our object?
             if(dwReturnMask & SCRIPTINFO_IUNKNOWN) {
                // Check for our object name...
                if (!_wcsicmp(L"MyObject", pstrName)) {
                   // Provide our object.
                   *ppunkItem = m_pUnkScriptObject;
                   // Addref our object...
                   m_pUnkScriptObject->AddRef();
                }
             }
          }
          
          return S_OK;
       }
       
       virtual HRESULT __stdcall GetDocVersionString(BSTR *pbstrVersion) {
          return S_OK;
       }
       
       virtual HRESULT __stdcall OnScriptTerminate(const VARIANT *pvarResult,
          const EXCEPINFO *pexcepInfo) {
          return S_OK;
       }
       
       virtual HRESULT __stdcall OnStateChange(SCRIPTSTATE ssScriptState) {
          return S_OK;
       }
       
       virtual HRESULT __stdcall OnScriptError(
          IActiveScriptError *pscriptError) {
          static BSTR pwcErrorText;
          pscriptError->GetSourceLineText(&pwcErrorText);
    
          AfxMessageBox(
             CString("IActiveScriptSite::OnScriptError()\n") + 
             CString("Line: ") + 
             CString(pwcErrorText),
             MB_SETFOREGROUND);
          ::SysFreeString(pwcErrorText);
          return S_OK;
       }
       
       virtual HRESULT __stdcall OnEnterScript(void) {
          return S_OK;
       }
       
       virtual HRESULT __stdcall OnLeaveScript(void) {
          return S_OK;
       }
    };
    
    // Global instance of our IActiveScriptSite implementation.
    MyActiveScriptSite g_iActiveScriptSite;
    
    // Script Engine CLSIDs...
    #include <initguid.h>
    DEFINE_GUID(CLSID_VBScript, 0xb54f3741, 0x5b07, 0x11cf, 0xa4, 0xb0, 0x0,
       0xaa, 0x0, 0x4a, 0x55, 0xe8);
    DEFINE_GUID(CLSID_JScript, 0xf414c260, 0x6ac0, 0x11cf, 0xb6, 0xd1, 0x00,
       0xaa, 0x00, 0xbb, 0xbb, 0x58);
    
    // Ole-initialization class.
    class OleInitClass {
    public:
       OleInitClass() {
          OleInitialize(NULL);
       }
       ~OleInitClass() {
          OleUninitialize();
       }
    };
    // This global class calls OleInitialize() at
    // application startup, and calls OleUninitialize()
    // at application exit...
    OleInitClass g_OleInitClass;
    
    void HRVERIFY(HRESULT hr, char * msg)
    {
       if(FAILED(hr)) {
          CString str;
          str.Format("Error: 0x%08lx (%s)", hr, msg);
          AfxMessageBox(str, 0x10000);
          _exit(0);
       }
    
    }
    					
  12. Compile and run.

After you build the project, run it, add the following VBScript to your application's edit box, then click the button:
   dim x
   dim y
   dim z
   x = 101*199
   y = 313*199
   z = gcd(x, y)

   ShowValue "gcd(" & x & ", " & y & ") = ", z

   HelpAbout
				


The basic work flow is as follows:
  1. You start the VBScript engine, vbscript.dll, and obtain IActiveScript and IActiveScriptParse interfaces.
  2. You give the VBScript engine your implementation of IActiveScriptSite, which the engine uses later to obtain and call to your objects.
  3. You add the objects that you implement and want to make available to scripts by calling IActiveScript::AddNamedItem().
  4. You provide the script text to execute through IActiveScriptParse::ParseScriptText(). Note that this doesn't actually run the script yet.
  5. The script engine will now call into your IActiveScriptSite::GetItemInfo() for any objects it doesn't recognize, to get their interface pointers.
  6. You call IActiveScript::SetScriptState() with SCRIPT_STATE_CONNECTED to run the script.
  7. The VBScript engine parses the text in the script for you and when it encounters a method call or property reference, it delegates the implementation to your provided interfaces.

REFERENCES

For additional information about Microsoft ActiveX Script Hosting, please see the following articles in the Microsoft Knowledge Base:

183698 SAMPLE: AXSH.EXE Demonstrates Implementing ActiveX Script Hosts

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


Modification Type:MajorLast Reviewed:6/3/2005
Keywords:kbActiveXScript kbhowto KB221992 kbAudDeveloper