ActiveX script hosting (2)

This code represents one of my current experiments with active script hosting. I am not sure if the code will be useful for you, i am not even sure if the code will be useful for me either. The explanation of my uncertainty is in the readme.txt file, packed along with the project. I have prepared this article not just to show you an example, but mostly to ask you some questions being in hope for receiving some answers.

In this experiment i have tried to make a program that would use the Microsoft Forms 2.0 designer, along with abilities to control the form, created by the designer, with programs written in VBScript.

When you run the program open the file "TestForm.frm". This is an example of form. You can use the menu item "View|Design mode" to edit form's design, and the menu item "View|Edit script" to see the VBScript source of the form.

I have tested the program under NT 4.0 and Windows 95 (4.00.950). Under NT4 everything goes ok, but under Windows 95 i see messages during execution in the debug window telling:

First-chance exception in MsForm.exe (GDI32.DLL): 0xC0000005: Access Violation.

When i try to exit, the program causes a GPF in OLEAUTO.DLL and dies. I do not know so far what is wrong with my code.

I am not going to describe how to create active script hosts. There are some articles in Internet concerning the topic. I have even tried to describe it myself. Here i will try to explain how to add the MSForm designer to your MFC project, and how to connect the designer to the scripting engine.

If you run the "OLE-COM Object Viewer" program and select the menu item "File|View TypeLib..." and select the "FM20.DLL" file located in your Windows System folder, you will see the coclass "UserForm". Observe that the interface to the "UserForm" coclass (_UserForm) has been inherited form the interface to the "Frame" colass (IOptionFrame). Having noticed this feature, i have made some steps for creating an MFC wrapper class for MSForm designer in my project.

  1. In the Developer studio selected the menu item "Project|Add to project|Components and controls".
  2. In the "Registered ActiveX Controls" folder selected the "Microsoft Forms 2.0 Frame" control. The MSDEV created some wrapper MFC classes to work with Microsoft controls.
  3. In all created files replaced the word "OptionFrame" with "UserForm". I have changed the names of the "optionframe.cpp" and "optionframe.h" files accordingly.
  4. Modified the CUserForm::Create functions with new ones which use CLSID of the "UserForm" coclass.
  5. Manualy implemented the hidden "DesignMode" property of the form.

Thus i have created the MFC wrapper class for MSForm control. The name of the wrapper class is CUserForm.

My next step was creating the four classes CMsFormDoc, CMsFormView, CVbscriptView and CMSFormFrame.

The CMsFormDoc object almost does nothing except of serializing and providing links between FormView and VbscriptView objects.

The CVbscriptView is a simple editor to edit a vbscript code for a form.

The CMSFormFrame provides a way of containing two views - the MsFormView, and the CVbscriptView together in a single frame.

The CMsFormView object carries on and controls a CUserForm object and on user requests switches on and off the design mode of the form.

Take a look at the CMsFormView::GetUserFormUnknown() function. If you want to obtain the IUnknown interface to an object created by CWnd::CreateControl function, you will not be able to get it by calling the GetIDispatch function. CWnd object incapsulates the OLE control container features by some undocumented classes which are defined in the header file located in the MFC source folder.


//The code to obtain the IUnknown interface to the control
IUnknown* CMsFormView::GetUserFormUnknown()
{
	IUnknown* pUnk=m_UserForm.GetControlUnknown();
	return pUnk;
}

The two functions - CMsFormView::WriteDesignToFile(LPCTSTR lpstrFileName) and CMsFormView::ReadDesignFromFile(LPCTSTR lpstrFileName) implement the form control contents writing to and reading from a temporary file.

The CMsFormDoc::Serialize(CArchive& ar) function use these functions to store and load the design information.

Connecting the form with the scripting engine is not difficult. In this experiment i used the CScriptEngine class provided by .... I have made the CMsFormScriptEngine class derived form CScriptEngine to pass the information needed from the form to the scripting engine. An instance of the CMsFormScriptEngine is a part of CMsFormView class.


class CMsFormView : public CView
{
	.....
	CUserForm				m_UserForm;
	CMsFormScriptEngine		m_ScriptEngine;
};

The CMsFormView::Run function feeds the scripting engine with named items, script code, and switches it to the "connected" state. Here there is a problem. A scripting engine can process events from objects, referenced by the AddNamedItem function, and their properties - objects which can source events by themselves. The information about object's properties the scripting engine obtains from the type library. Since form designer has undefined set of properties, at the moment of compilation of the project, the type library must be created dynamicaly. Moreover, the IDispatch interface to such an object must support dynamic properties. The example of creating such an object and such a typelib is the "Axscript" project, located in the ActiveX SDK examples folder. I am not familiar with the ICreateTypeLib, ICreateTypeInfo and IProvideMultipleInfo interfaces. I failed to find any documentation to these interfaces. When i tried to repeat the code, shown in the example, whith MFC, i had not achieved the results i wanted.

I have solved the problem by adding references to all the controls in the form to the scripting engine. Observe that the reference to the "Form" named item is added with the SCRIPTITEM_GLOBALMEMBERS. All named items are added with the SCRIPTITEM_ISVISIBLE|SCRIPTITEM_ISSOURCE flags.


void CMsFormView::Run()
{
	if(!m_ScriptEngine.IsCreated())
		m_ScriptEngine.Create(CLSID_VBScript);
	else
	{
		SCRIPTSTATE ss=m_ScriptEngine.GetScriptState();
		if(ss==SCRIPTSTATE_CONNECTED)
			return;
	}

	CString strScript=GetDocument()->GetScriptText();
	if(!strScript.IsEmpty() && m_UserForm.GetDesignMode()!=-1)
	{
		HRESULT		hr;
		
		USES_CONVERSION;
		LPCOLESTR	pstrItemName = T2COLE(_T("Form"));
		LPCOLESTR	pstrCode = T2COLE(strScript);
		EXCEPINFO   ei;
		
		
		hr=m_ScriptEngine.AddNamedItem(pstrItemName, SCRIPTITEM_ISVISIBLE|SCRIPTITEM_ISSOURCE|SCRIPTITEM_GLOBALMEMBERS);
		if(FAILED(hr))
		{
			AfxMessageBox(_T("AddNamedItem failed. Item name: <Form>."));
			return;
		}
		
		//Adding form controls
		for(long i=0; i<m_UserForm.GetControls().GetCount(); i++)
		{
			CString strControlName=m_UserForm.GetControls()._GetItemByIndex(i).GetName();

			LPCOLESTR	pstrControlName;
			pstrControlName = T2COLE(strControlName);
			hr=m_ScriptEngine.AddNamedItem(pstrControlName,SCRIPTITEM_ISVISIBLE | SCRIPTITEM_ISSOURCE);
			if(FAILED(hr))
			{
				CString strError=_T("AddNamedItem failed. Item name: <");
				strError+=strControlName+_T(">");
				AfxMessageBox(strError);
				return;
			}
		}
			
		if(SUCCEEDED(m_ScriptEngine.ParseScriptText(pstrCode, pstrItemName, NULL, NULL, 0, 0, 0L, NULL, &ei)))
			m_ScriptEngine.Run();
		else
			AfxMessageBox(_T("Can't parse script text"));
	}
}

The interfaces to the named items are obtained by the scripting engine in the CMsFormScriptEngine::OnGetItemInfo function. The CMsFormScriptEngine object has a back pointer (m_pView) to the CMsFormView object in which it is contained. The technique of obtaining the class information of an object i have seen in several examples. I just do not understand why the IProvideClassInfo interface to the object is not released. When i tried to release the interface, my program caused a GPF.


IProvideClassInfo* pProvideClassInfo = NULL;
hr = pUnk->QueryInterface(IID_IProvideClassInfo, (void**)&pProvideClassInfo);
			
// if the object supports IprovideClassInfo ...
if (SUCCEEDED(hr) && pProvideClassInfo != NULL)
{
	hr = pProvideClassInfo->GetClassInfo(ppTypeInfo);
	
	//The pProvideClassInfo->Release() should not be called. Why?
}




/////////////////////////////////////////////////////////////////////////////
// CMsFormScriptEngine message handlers
HRESULT CMsFormScriptEngine::OnGetItemInfo(
		/* [in] */ LPCOLESTR   pstrName,
		/* [in] */ DWORD       dwReturnMask,
		/* [out]*/ IUnknown**  ppUnknownItem,
		/* [out]*/ ITypeInfo** ppTypeInfo)
{
	USES_CONVERSION;

	HRESULT hr=CScriptEngine::OnGetItemInfo(pstrName,dwReturnMask,ppUnknownItem,ppTypeInfo);
	if(!m_pView)
		return hr;

	hr=TYPE_E_ELEMENTNOTFOUND;
	if (dwReturnMask & SCRIPTINFO_ITYPEINFO)
	{
		if (!ppTypeInfo)
			return E_INVALIDARG;
		*ppTypeInfo = NULL;
	}

	if (dwReturnMask & SCRIPTINFO_IUNKNOWN)
	{
		if (!ppUnknownItem)
			return E_INVALIDARG;
		*ppUnknownItem = NULL;
	}


	TCHAR* lpstrItemName=OLE2T(pstrName);
	IUnknown* pUnk=0;

	if(!_tcscmp(_T("Form"), lpstrItemName))
	{
		pUnk=m_pView->GetUserFormUnknown();
	}
	else
	{
		CControls Controls=m_pView->m_UserForm.GetControls();
		for(long i=0; i<Controls.GetCount(); i++)
		{
			CControl C=Controls._GetItemByIndex(i);
			CString strControlName=C.GetName();
			
			if(strControlName==lpstrItemName)
			{
				pUnk=C.m_lpDispatch;
				break;
			}	
			
		}
	}

	if(pUnk)
	{
		if (dwReturnMask & SCRIPTINFO_ITYPEINFO)
		{
			IProvideClassInfo* pProvideClassInfo = NULL;
			hr = pUnk->QueryInterface(IID_IProvideClassInfo, (void**)&pProvideClassInfo);
			
			// if the object supports IprovideClassInfo ...
			if (SUCCEEDED(hr) && pProvideClassInfo != NULL)
			{
				hr = pProvideClassInfo->GetClassInfo(ppTypeInfo);
			}
		}

		if (dwReturnMask & SCRIPTINFO_IUNKNOWN)
		{
			pUnk->AddRef();
			*ppUnknownItem=pUnk;
			hr=S_OK;
		}
	}

	return hr;
}

To enable standard keyboard handling by forms, i have implemented the CMsForm::PreTranslateMessage function. It is alomost the same as CFormView::PreTranslateMessage, but i had some problems with typing in text boxes, so i had to make some alterations.


BOOL CMsFormView::PreTranslateMessage(MSG* pMsg) 
{
	// TODO: Add your specialized code here and/or call the base class
	// allow tooltip messages to be filtered
	if (CView::PreTranslateMessage(pMsg))
		return TRUE;

	// don't translate dialog messages when in Shift+F1 help mode
	CFrameWnd* pFrameWnd = GetTopLevelFrame();
	if (pFrameWnd != NULL && pFrameWnd->m_bHelpMode)
		return FALSE;

	// since 'IsDialogMessage' will eat frame window accelerators,
	//   we call all frame windows' PreTranslateMessage first
	pFrameWnd = GetParentFrame();   // start with first parent frame
	while (pFrameWnd != NULL)
	{
		// allow owner & frames to translate before IsDialogMessage does
		if (pFrameWnd->PreTranslateMessage(pMsg))
			return TRUE;

		// try parent frames until there are no parent frames
		pFrameWnd = pFrameWnd->GetParentFrame();
	}

	
	//To allow typing in text boxes
	if (pMsg->message == WM_CHAR)
		return FALSE;

	// filter both messages to dialog and from children
	return PreTranslateInput(pMsg);
}

That's all. If you have any information concerning the ActiveX script hosting technology or dynamic properties support, please send me a message.

Useful References :-

  1. Microsoft Support - ActiveX Scripting Download Site
  2. Visual C++ Developers's Journal May 97 - Steve Wampler's article on Active X Script Hosting


Downloads

Comments

  • Change menu name

    Posted by jayender.vs on 12/26/2005 12:40am

    Hi , I saw ActiveX scripting. Too good . i got a doubt ..is it possible to chage the menu names through VB scripting? if yes how to do it ? say for example the menu has got File,View,Action,Help.now i got to change the name of the menu (File,View.. etc) to something like X,Y,Z .. through VB scripting at run time. how to do it ? waiting for ur reply .

    Reply
  • Download file is corrected

    Posted by CG Susan on 12/09/2004 06:07pm

    Apologies from the CodeGuru administrators! The original download file was an EXE file and our new system only allows ZIP files. The source code should download properly now. Please contact submit@codeguru.com if you experience any problems downloading the file.

    Reply
  • Download file not found

    Posted by evm on 11/27/2004 10:03am

    Download file not found

    Reply
  • Download file not found

    Posted by 73spica on 05/09/2004 10:18pm

    Could you please send me the source code and/or an example project by email? Thank you very much in advance

    • Need file

      Posted by kfoust on 11/07/2004 10:54pm

      Could you please send me the source code and/or an example project by email (ken_foust@hotmail.com)? Thank you very much in advance

      Reply
    Reply
  • I want to run script in UNIX Plafrom.

    Posted by Legacy on 02/06/2003 12:00am

    Originally posted by: kang, oh-sik

    I want to run script in UNIX Plafrom.

    Can i this ?

    Would you recommand if you have another soluation?

    Reply
  • How to add other control to msform?

    Posted by Legacy on 04/14/2002 12:00am

    Originally posted by: Wang Jun

    Msform can draw some controls,but i want to add other control into it,who can help me ,thanks!

    Reply
  • Download file not found

    Posted by Legacy on 01/16/2002 12:00am

    Originally posted by: Dirk Schnorpfeil

    Could you please send me the source code and/or an example project by email?

    Thank you very much in advance,

    Dirk Schnorpfeil

    Reply
  • A bug in BOOL CMsFormView::WriteDesignToFile(LPCTSTR lpstrFileName)

    Posted by Legacy on 07/05/2001 12:00am

    Originally posted by: raoxh

    It is a very useful example, and there is a bug in it;
    
    

    BUG: When save the form twice rapidly(or on a lower computer), or save the form under design mode, the saved file cannot be used.

    Correct:Add a line in the function

    BOOL CMsFormView::WriteDesignToFile(LPCTSTR lpstrFileName)
    {
    USES_CONVERSION;

    LPSTORAGE lpStorage;
    SCODE sc = ::StgCreateDocfile(T2COLE(lpstrFileName),
    STGM_READWRITE|STGM_TRANSACTED|STGM_SHARE_EXCLUSIVE|STGM_CREATE,
    0, &lpStorage);
    if (sc != S_OK)
    AfxThrowOleException(sc);

    IUnknown* pUnk=GetUserFormUnknown();
    IPersistStorage* pPersistStorage=0;
    pUnk->QueryInterface(IID_IPersistStorage,(void**)&pPersistStorage);

    BOOL bOk=SUCCEEDED(pPersistStorage->Save(lpStorage,FALSE));

    //////////////////////////////////////////////////////
    //Add line As
    pPersistStorage->SaveCompleted(NULL);
    //////////////////////////////////////////////////////

    lpStorage->Commit(STGC_DEFAULT);

    pPersistStorage->Release();
    lpStorage->Release();

    return bOk;
    }

    Reply
  • Dynamic control Resizing

    Posted by Legacy on 03/24/1999 12:00am

    Originally posted by: Mark Ward

    When Microsoft use the FM20.DLL formbuilder it seems to
    be for designing dialog boxes that are not resizable. To
    use the FM20.DLL in the context of a form view application
    it would be nice to allow controls to resize or reposition
    so they are not clipped or hidden when the main app
    window is resized.

    How could it be done using your FM20.DLL code ?

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • The explosion in mobile devices and applications has generated a great deal of interest in APIs. Today's businesses are under increased pressure to make it easy to build apps, supply tools to help developers work more quickly, and deploy operational analytics so they can track users, developers, application performance, and more. Apigee Edge provides comprehensive API delivery tools and both operational and business-level analytics in an integrated platform. It is available as on-premise software or through …

  • Not long ago, security was viewed as one of the biggest obstacles to widespread adoption of cloud-based deployments for enterprise software solutions. However, the combination of advancing technology and an increasing variety of threats that companies must guard against is rapidly turning the tide. Cloud vendors typically offer a much higher level of data center and virtual system security than most organizations can or will build out on their own. Read this white paper to learn the five ways that cloud …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds