Microsoft KB Archive/221992

= How to add support for hosing VBScript to your MFC application =

Article ID: 221992

Article Last Modified on 6/4/2005

-

APPLIES TO

 Microsoft Foundation Class Library 4.2, when used with:  Microsoft Visual C++ 5.0 Enterprise Edition

 Microsoft Visual C++ 6.0 Enterprise Edition

 Microsoft Visual C++ 5.0 Professional Edition

 Microsoft Visual C++ 6.0 Professional Edition</li></ul>

 Microsoft Visual C++ 6.0 Standard Edition</li></ul> </li></ul>

-

<div class="notice_section">

This article was previously published under Q221992

<div class="summary_section">

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.

<div class="moreinformation_section">

MORE INFORMATION
Follow the steps below to build and run the example:

<ol> Create a new MFC dialog-based application, or use an application you already have to which you would like to add scripting support.</li> Add an edit box and button to your dialog, and enable the "Want return" and "Multiline" styles in the properties for the edit box.</li> Use ClassWizard (CTRL-W), under the Member Variables tab, to associate IDC_EDIT1 with a member variable of type CEdit named m_edit1.</li> 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.</li> 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);

</li>  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); }                   </li> Open MyScriptObject.h, and move the virtual destructor ~MyScriptObject from the protected section of the class to a public section.</li>  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; </li>  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; </li>  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"                   </li>  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... 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);
 * 1) include <initguid.h>

// 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); }

}                   </li> Compile and run.</li></ol>

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.

<div class="references_section">