DOCUMENT:Q221992 01-AUG-2001 [visualc] TITLE :HOWTO: Add Support for Hosting VBScript to Your MFC Application PRODUCT :Microsoft C Compiler PROD/VER:winnt:5.0,6.0 OPER/SYS: KEYWORDS:kbole kbActiveX kbAXScript kbCOMt kbJScript kbMFC kbVBScript kbVC500 kbVC600 kbGrpDSO k ====================================================================== ------------------------------------------------------------------------------- The information in this article applies to: - Microsoft Visual C++, 32-bit Enterprise Edition, versions 5.0, 6.0 - Microsoft Visual C++, 32-bit Professional Edition, versions 5.0, 6.0 - Microsoft Visual C++, 32-bit Learning Edition, version 6.0 - The Microsoft Foundation Classes (MFC) ------------------------------------------------------------------------------- SUMMARY ======= You can add VBA-like scripting capability to your MFC application with little overhead, using 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 // 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 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: Q183698 SAMPLE: AXSH.EXE Demonstrates Implementing ActiveX Script Hosts Q168214 SAMPLE: MFCAXS Implements an Active Script Host Using MFC (c) Microsoft Corporation 1999, All Rights Reserved. Contributions by Joe Crump, Microsoft Corporation Additional query words: vba sdk ====================================================================== Keywords : kbole kbActiveX kbAXScript kbCOMt kbJScript kbMFC kbVBScript kbVC500 kbVC600 kbGrpDSO kbActiveXScript Technology : kbVCsearch kbAudDeveloper kbMFC kbVC500 kbVC600 kbVC32bitSearch kbVC500Search Version : winnt:5.0,6.0 Issue type : kbhowto ============================================================================= THE INFORMATION PROVIDED IN THE MICROSOFT KNOWLEDGE BASE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND. MICROSOFT DISCLAIMS ALL WARRANTIES, EITHER EXPRESS OR IMPLIED, INCLUDING THE WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL MICROSOFT CORPORATION OR ITS SUPPLIERS BE LIABLE FOR ANY DAMAGES WHATSOEVER INCLUDING DIRECT, INDIRECT, INCIDENTAL, CONSEQUENTIAL, LOSS OF BUSINESS PROFITS OR SPECIAL DAMAGES, EVEN IF MICROSOFT CORPORATION OR ITS SUPPLIERS HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. SOME STATES DO NOT ALLOW THE EXCLUSION OR LIMITATION OF LIABILITY FOR CONSEQUENTIAL OR INCIDENTAL DAMAGES SO THE FOREGOING LIMITATION MAY NOT APPLY. Copyright Microsoft Corporation 2001.