ActiveX script hosting

In this article will be described a way of implementing of an ActiveX script host with MFC.

An ActiveX script host is an application that can run some code written in other language (VBScript, VBA, Java script) and can expose the application's internal COM to the script being runned.

The Micrososft's Internet Explorer, Excel, Word, Access - are ActiveX script hosts. The first one use VBScript and the other ones use VBA.

Most of the code concerning the idea how to implement a host was taken from the "Axscript" example which is located on your Developer Studio CD. The example was implemented with WIN32 API. I have prepared three projects to describe the implementation of an ActiveX script with MFC.

STEP1 - an example how to make your MFC program run VBScript code.
STEP2 - exposing internal automation objects to the VBScript engine.
STEP3 - adding ability to handle events fired in C++ code by the VBScript code.

To see how the program works, compile the projects one after another and run. Having run the program, select the menu item "VBScript-> Load script file". You will be proposed to select a file with the script. Select the "TestProgram.txt" file which is located in the directory of the project. Then select the "VBScript->Run" menu item to run the script.

The first example would display a message box. The second one would draw several rectangular sprites. The third one would allow you to drag sprites with the mouse.

Note. If you make your project settings to place the results of all the projects in the same folder, do not forget to use the "Rebuild All" menu item when switching to another project. Otherwise the program will not work properly.

To understand clearly how an ActiveX scripting engine works see help in the Developer Studio. The information is available at "Platform, SDK and DDK -> ActiveX SDK ->ActiveX scripting".

Step1

To make your program run VBScript code (or other script) you have to deal with the script engine which can be available through IActiveScript and IActiveScriptParse interfaces. Also your program must expose an object with IActiveScriptSite and optionaly IActiveScriptSiteWindow interfaces.

The "Step1" example has the CMainFrame object which supports both IActiveScript and IActiveScriptParse interfaces.

Most of the IActiveScript methods in this example were not implemented. To understand how to implement OLE interfaces with MFC see the technical note number 38.

Step2

In this step there were two automation objects created with ClassWizard. The first object is the CAXHostDoc was derived from CDocument. The second one is the CAXSprite object was derived from CCmdTarget. CAXHostDoc has an object called Animation which can manage sprites. CAXSprite is an OLE wrapper for a rectangular sprite. Sprites can be added to the Animation object, removed, moved, changed, ... but Animation does nothing until you ask it to render an image to a particular DC. Do not care about how it was implemented.

Your program can add to the scripting engine some top level item names, which will be available in the script. This item names should be associated with some COM objects in your application. Usualy i use the "Application" object as top level item. But in this example, which is an SDI application, the CAXHostApp object has a member m_pDoc to hold a pointer to the document. In the CMainFrame::CreateScriptEngine method the "Document" item name has been added to the script engine.


const TCHAR* szItemName=_T("Document");

USES_CONVERSION;
LPCOLESTR	lpostrApp = T2COLE(szItemName);
hr=m_pIActiveScript->AddNamedItem(lpostrApp, SCRIPTITEM_ISVISIBLE);
The association between the item name added and the COM object is made in the IActiveScriptSite::GetItemInfo method.

STDMETHODIMP CMainFrame::XScriptSite::GetItemInfo
(
  LPCOLESTR   pstrName,
  DWORD       dwReturnMask,
  IUnknown**  ppunkItemOut,
  ITypeInfo** pptinfoOut
)
{
	//HRESULT hr;
	METHOD_PROLOGUE(CMainFrame, ScriptSite)

	USES_CONVERSION;

	LPCTSTR lpstrApplication=szItemName;
    
	if (dwReturnMask & SCRIPTINFO_ITYPEINFO)
	{
		if (!pptinfoOut)
			return E_INVALIDARG;
		*pptinfoOut = NULL;
	}

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

		LPTSTR lpszName = OLE2T(pstrName);

//Look here, please /////////////    
		if(!_tcsicmp(lpstrApplication, lpszName))
		{
			*ppunkItemOut = theApp.m_pDoc->GetIDispatch(TRUE);
			return S_OK;
////////////////////////////
		}		
    }
	

	return TYPE_E_ELEMENTNOTFOUND;
}
As soon as the association has been made, you can use the Documents methods and properties. If there are some methods which return interfaces to other COM objects in your application, you can use their methods and properties without AddNamedItem function. They are not top level objects. They was obtained from the Document object (in this example there is just one such object - CAXSprite).

Step 3

In this example we make our program able to fire some events which can be handled by the VBScript engine. First of all we will need to provide ITypeInfo information to the script engine. The application object has some members to hold this information (you can use global variables).

ITypeLib*	pITypeLib;    //library AXHost
ITypeInfo*	ptinfoClsDoc; //coclass Document
ITypeInfo*	ptinfoIntDoc; //dispinterface IAXHost

ITypeInfo*	ptinfoClsSprite; //coclass Sprite
ITypeInfo*	ptinfoIntSprite;	//dispinterface IAXSprite

To make your object fire events you need to create another interface to your object's events. Modify your ODL file.

Example:


	[uuid(B6032E41-636A-11d1-8F0B-F54176DCF130)]
	dispinterface  IAXHostEvents
	{
		properties:
		methods: // Events
		[id(1)]  void OnMouseDown([in] short x, [in] short y);
		[id(2)]  void OnMouseMove([in] short x, [in] short y);
		[id(3)]  void OnMouseUp([in] short x, [in] short y);
		[id(4)]  void OnMouseDblClk([in] short x, [in] short y);
	}

	//  Class information for CAXHostDoc
	
	[ uuid(34E25943-6314-11D1-8F0B-F54176DCF130) ]
	coclass Document
	{
		[default] dispinterface IAXHost;
		[default, source]  dispinterface IAXHostEvents;
	};

In the CAXHostApp::InitInstance() method the type information was loaded


// ##### BEGIN ACTIVEX SCRIPTING SUPPORT #####
const TCHAR* lpstrTLB=_T("AXHost.tlb");
if(S_OK!=LoadTypeInfo(lpstrTLB,0,1,0,0,IID_LIBAXHost,IID_IAXHostClass,IID_IAXHost,FALSE,&pITypeLib,&ptinfoClsDoc,&ptinfoIntDoc))
	return FALSE;

if(S_OK!=LoadTypeInfo(lpstrTLB,0,1,0,0,IID_LIBAXHost,IID_IAXSpriteClass,IID_IAXSprite,FALSE,&pITypeLib,&ptinfoClsSprite,&ptinfoIntSprite))
	return FALSE;
// #####  END  ACTIVEX SCRIPTING SUPPORT #####

In the MainFrm.module:

The AddNamedItem was called with the SCRIPTITEM_ISSOURCE flag.


pThis->m_pIActiveScript->AddNamedItem(lpszT, SCRIPTITEM_ISVISIBLE|SCRIPTITEM_ISSOURCE);

In the IActiveScriptSite::GetItemInfo ITypeInfo interface to the Document CLASS object has been provided to the scripting engine.


STDMETHODIMP CMainFrame::XScriptSite::GetItemInfo
(
  LPCOLESTR   pstrName,
  DWORD       dwReturnMask,
  IUnknown**  ppunkItemOut,
  ITypeInfo** pptinfoOut
)
{
	//HRESULT hr;
	METHOD_PROLOGUE(CMainFrame, ScriptSite)

	USES_CONVERSION;

	LPCTSTR lpstrApplication=szItemName;
    
	if (dwReturnMask & SCRIPTINFO_ITYPEINFO)
	{
		if (!pptinfoOut)
			return E_INVALIDARG;
		*pptinfoOut = NULL;

//Attention, please
		LPTSTR lpszName = OLE2T(pstrName);

		if(!_tcsicmp(lpstrApplication, lpszName))
		{
			*pptinfoOut=theApp.ptinfoClsDoc;
			return S_OK;
		}
////////////////////
	}

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

//Attention, please
		LPTSTR lpszName = OLE2T(pstrName);
    
		if(!_tcsicmp(lpstrApplication, lpszName))
		{
			*ppunkItemOut = theApp.m_pDoc->GetIDispatch(TRUE);
			return S_OK;
		}		
///////////////////
    }
	

	return TYPE_E_ELEMENTNOTFOUND;
}


Also the IActiveScriptSite::RequestTypeLibs method was implemented.

//---------------------------------------------------------------------------
// 
//---------------------------------------------------------------------------
STDMETHODIMP CMainFrame::XScriptSite::RequestTypeLibs(void)
{
	METHOD_PROLOGUE(CMainFrame,ScriptSite);
	HRESULT hr=pThis->m_pIActiveScript->AddTypeLib(IID_LIBAXHost, 1, 0, 0);
	return hr;
}

If you want your object support events, it must support IConnectionPointContainer interface and IProvideTypeInfo interface. See the article about CConnectionPoint MFC object. See how it was implemented with the CAXHostDoc object.

If you implement all the interfaces correctly, the scripting engine will establish a connection to your object and you will be able to fire events with help of a small function.


void CAXHostDoc::FireEvent(int iEvNum,VARIANTARG* pVar,int nParameters)
{
	const CPtrArray* pConnections = m_xConnPt.GetConnections();
    ASSERT(pConnections != NULL);

    int cConnections = pConnections->GetSize();
    IDispatch* pSink;
    for (int i = 0; i < cConnections; i++)
    {
        pSink = (IDispatch*)(pConnections->GetAt(i));
        ASSERT(pSink != NULL);
		InvokeEvent(pSink, iEvNum, pVar, nParameters);
    }
}

The iEvNum must be the same as in the ODL file. To fire the MouseDown event, for instance, i have to use iEvNum to be equal 1. Because [id(1)] void OnMouseDown([in] short x, [in] short y);

I should have made constants like IDEVENT_MOUSEDOWN and use them both in the ODL file and in my C++ code.

Example:


void CAXHostView::OnLButtonDown(UINT nFlags, CPoint point) 
{
	// TODO: Add your message handler code here and/or call default
	SetCapture();
	VARIANTARG  var[2];

	VariantInit(&var[1]);
	var[1].vt = VT_I2;
	var[1].iVal = (short)point.x;
	VariantInit(&var[0]);
	var[0].vt = VT_I2;
	var[0].iVal = (short)point.y;

	GetDocument()->FireEvent(1,var,2);
	
	CView::OnLButtonDown(nFlags, point);
}

Observe the sequence of passing parameters to the variant array.

I am not sure that in this small description i have written all the tips to make your program work, but i send you the code which you can investigate. And you should alse see the "Axscript" example which does a little bit more. It provides a way how to make subclassing with VBScript. See the BuildTypeInfo function in the example to see how it has been done there.

If you have any questions, you can send me a message and i will try to answer. But i must confess that i am a novice in COM. COM is not difficult to understand but there are so many different objects and interfaces. And one must know how to use them in one's programs. The same can be said about the C++ language itself.

I also would like to get some examples on this matter if you have them. For example, i don't know how to implement debugging features (they say that Microsoft has already made a debugger fo VBScript), how to interrupt a running script (i think i should create an another thread and use an appropriate function), i tried to implement DUAL interfaces to my objects but the VBScript used just the IDispatch interfaces and i thought that VBScript did not support DUAL interfaces.

If you have any clues concerning this technology, please send them to me. I would like also your opinion whether this feature is interesting, for i can send some more examples. I mean not this sprites but a way of using this technology in business applications. For example, my program use special kinds of objects to work with SQL server databases and Access databases such as "recordsets", "idsets, "caches", "reports", "views" and others.

Download file. This is a compressed executable. It will the three directories for the three steps. Size: 109KB



Comments

  • Here's the proper link

    Posted by introp on 08/18/2005 05:48pm

    Try: http://www.codeguru.com/code/legacy/atl/axhost.exe

    Reply
  • I am not able to download attachment too

    Posted by highlander0 on 09/29/2004 07:40am

    I am not able to download attachment too.

    Reply
  • Readers can also go to MSDN here for more info, including implementing Debug functionality

    Posted by Legacy on 01/20/2004 12:00am

    Originally posted by: Zhefu Zhang

    here: http://msdn.microsoft.com/msdnmag/issues/1200/active/default.aspx

    it is MSDN mag 2000 Dec

    Reply
  • Good Work

    Posted by Legacy on 10/01/2003 12:00am

    Originally posted by: Akash Kava

    I was looking for exactly same solution and I found it here !!! Great peice of work

    - Akash Kava

    Reply
  • correct URL

    Posted by Legacy on 06/01/2001 12:00am

    Originally posted by: chi

    it should be this instead:
    http://www.codeguru.com/atl/axhost.exe

    • download file not found

      Posted by 73spica on 05/11/2004 09:36pm

      I can't download .exe file included with this article. please, send me by mail...

      Reply
    Reply
  • ActiveX Programming

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

    Originally posted by: Neo

    http://www.codeguru.com/activex/axhost.exe


    broken link


    Can't download file

    Reply
  • page not found

    Posted by Legacy on 04/17/2001 12:00am

    Originally posted by: Craig Lemon

    Hi,

    When I try to download the .exe file included with this
    article, I get a page not found error. Does anyone have
    a copy of this file that they could e-mail to me. I have
    tried e-mailing the author, but as yet, have not received a
    reply.

    thanks in advance,
    Craig

    Reply
  • Adding Methods

    Posted by Legacy on 03/18/2000 12:00am

    Originally posted by: Luca Benedetto

    I'm try the example of this article, but, in ex #2, when I try to add a new methode i was'nt able to add a method with 2 params [for ex. i'm able to add a method as Print(char *printthis), but i'm not able to add Print(char *printthis, short ThisNTimes)].
    I've verify that my way to add the method (ClassWizard) was the correct, but I'm not able to understand why dos'nt work with more then 1 params (in this case the VBS engine say "In not correct to use ) in sub call").

    Cna anybody help me?

    Reply
  • Comments on Event Firing

    Posted by Legacy on 10/10/1998 12:00am

    Originally posted by: Pyasetsky Rafael

    First of all I was very glad to encounter this samples.
    
    The article by Steve Wampler "ActiveX Scripting in MFC" from VCDJ
    contains incomplete MFC sample without Event processing
    (most interestring feature).

    I have suggestion on implementation of Event Firing.
    The idea consists of to accomodate a driver class generated by ClassWizard from event source dispinterface of project's type library.
    So for example import event source dispinterface -
    through dialog "MFC Class Wizard" -> AddClass->"From a type library..."
    generates class - IAXHostEvents derived from COleDispatchDriver,
    in which every Event method is encupsulated in C++ member function.
    In our case:
    // IAXHostEvents wrapper class

    class IAXHostEvents : public COleDispatchDriver
    {
    public:
    IAXHostEvents() {} // Calls COleDispatchDriver default constructor
    IAXHostEvents(LPDISPATCH pDispatch) : COleDispatchDriver(pDispatch) {}
    IAXHostEvents(const IAXHostEvents& dispatchSrc) : COleDispatchDriver(dispatchSrc) {}

    // Attributes
    public:

    // Operations
    public:
    void OnMouseDown(short X, short Y);
    void OnMouseMove(short X, short Y);
    void OnMouseUp(short X, short Y);
    void OnMouseDblClk(short X, short Y);
    } ;


    /////////////////////////////////////////////////////////////////////////////
    // IAXHostEvents operations

    void CAXHostDoc::OnMouseDown(short X, short Y)
    {
    static BYTE parms[] =
    VTS_I2 VTS_I2;
    InvokeHelper(0x1, DISPATCH_METHOD, VT_EMPTY, NULL, parms,
    X, Y);
    }

    void CAXHostDoc::OnMouseMove(short X, short Y)
    {
    static BYTE parms[] =
    VTS_I2 VTS_I2;
    InvokeHelper(0x2, DISPATCH_METHOD, VT_EMPTY, NULL, parms,
    X, Y);
    }

    void CAXHostDoc::OnMouseUp(short X, short Y)
    {
    static BYTE parms[] =
    VTS_I2 VTS_I2;
    InvokeHelper(0x3, DISPATCH_METHOD, VT_EMPTY, NULL, parms,
    X, Y);
    }

    void CAXHostDoc::OnMouseDblClk(short X, short Y)
    {
    static BYTE parms[] =
    VTS_I2 VTS_I2;
    InvokeHelper(0x4, DISPATCH_METHOD, VT_EMPTY, NULL, parms,
    X, Y);
    }


    Let's now move these function into the Event Source Class -( CAXHostDoc ) by only changing the class scope.

    To make final step, add helper function:
    // helpers for IDispatch::Invoke
    void AFX_CDECL InvokeHelper(DISPID dwDispID, WORD wFlags,
    VARTYPE vtRet, void* pvRet, const BYTE* pbParamInfo, ...);

    to Event Source Class ( CAXHostDoc ) that loops external event connections and repeats ready fire for each connection.


    void AFX_CDECL CAXHostDoc::InvokeHelper(DISPID dwDispID, WORD wFlags,
    VARTYPE vtRet, void* pvRet, const BYTE* pbParamInfo, ...)
    {
    va_list argList;
    va_start(argList, pbParamInfo);
    const CPtrArray* pConnections = m_xConnPt.GetConnections();
    ASSERT(pConnections != NULL);

    int cConnections = pConnections->GetSize();
    IDispatch* pSink;
    for (int i = 0; i < cConnections; i++)
    {
    pSink = (IDispatch*)(pConnections->GetAt(i));
    ASSERT(pSink != NULL);

    COleDispatchDriver(pSink, FALSE).InvokeHelperV(dwDispID, wFlags, vtRet, pvRet, pbParamInfo, argList);


    }

    As result the Event Source Class has the member functions
    for fireing every Event towards all connection.
    This method seems to resamble MFC style and I think might be implemented as ClassWizard support for Automation.
    Strange that similar DevStudio facilities have not been created yet.
    but ActiveX Controls have such support.

    Best regards. Pyasetsky R.

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

Top White Papers and Webcasts

  • Live Event Date: May 6, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT While you likely have very good reasons for remaining on WinXP after end of support -- an estimated 20-30% of worldwide devices still are -- the bottom line is your security risk is now significant. In the absence of security patches, attackers will certainly turn their attention to this new opportunity. Join Lumension Vice President Paul Zimski in this one-hour webcast to discuss risk and, more importantly, 5 pragmatic risk mitigation techniques …

  • Given today's threat environment and the increasing connectivity of digital infrastructures, security teams now realize that they must assume their IT environments are subject to periodic compromise. Gone are the days when preventive measures to secure the perimeter or trying to detect malware problems using signature-match technologies were enough. New practices based on an understanding of the phases of an attack, continuous threat monitoring, and rapid attack detection and remediation are necessary. This …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds