Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Extending the Internet Explorer Scripting Engine

0.00/5 (No votes)
27 Feb 2003 1  
This article shows you how to extend IE's scripting engine by adding custom event sinks and objects

Sample Image - dispexsinkconnector.gif

Introduction

Hosting the web browser control is cool. However, one of the limitations of browser hosting is the relatively limited access to container object. The standard way of talking to the container's IDispatch interface is via document.external. This is nice for providing basic extensions, but what about sinking events? Using custom enumeration types? Adding your own objects?

This article will show you how to add your own automation objects to any scripting engine through the IDispatchEx interface. It will also show you how you can easily sink COM events from script provided in your HTML page.

I will use the scripting engine from Internet Explorer to demonstrate functionality of the source code, but it is useful in any application that uses scripting.

How it Works

The CDispExSinkConnector class is what makes this all possible. It works by hooking into the script's IDispatchEx interface. New items are inserted into the scripting namespace by using the InvokeEx method. This extremely powerful method allows you add nearly any type of data to the scripting engine at runtime.

The first step is to provide the CDispExSinkConnector object with the IDispatchEx interface to use to access the scripting engine. This interface can be retrieved from Internet Explorer using the IHTMLDocument::get_Script() method, which returns an IDispatch interface which can then be QueryInterface()'d for an IDispatchEx.

IDispatchEx::GetDispID() is used to create new members inside the scripting engine. Once the member DISPID has been created, InvokeEx() is used to set the property value.

In order to sink events, CDispExSinkConnector creates a custom implementation of IDispatch which loads the type library containing the event dispinterface. When event methods are fired, the object will search the script namespace for a function matching event name preceeded by the prefix passed to ConnectObject(). If found, the function is then invoked.

You may be wondering about memory and interface leaks. The CDispExSinkConnector object itself implements a skeleton IDispatch interface and is inserted into the scripting engine when SetDispEx() is called, which maintains the object's persistence. It is automatically removed when the IE scripting engine is reset on a subsequent page load.

Class Layout

The important public methods of CDispExSinkConnector are:

CDispExSinkConnector :
    public IDispatch
{
    ...
    HRESULT ConnectObject(IUnknown *pUnk, BSTR bstrPrefix, 
        const GUID *piid, const GUID *plibid, 
        WORD wMajorVer = 1, WORD wMinorVer = 0, LCID lcid = 1033);
    HRESULT DisconnectObject(IUnknown *pUnk, const GUID *piid);

    HRESULT AddNamedObject(BSTR bsName, IDispatch *pDisp);
    HRESULT RemoveNamedObject(BSTR bsName);

    HRESULT AddTypeLib(REFGUID guidTypeLib, WORD wMaj, WORD wMin);

    void SetEnabled(BOOL bEnabled);
    BOOL GetEnabled();

    void SetDispEx(IDispatchEx *pDispEx);
    BOOL GetDispEx(IDispatchEx **ppDispEx);
    
    ...
};

Method Summary

ConnectObject() will connect the event source object specified by the pUnk parameter to the scripting engine. When an event is fired by the source, CDispExSinkConnector will search for a function in the scripting namespace named bstrPrefix + eventFunction. So for example, if I wanted to hook up my connectable object which has one event method called OnNumberChanged, and I specified "ExtObject_" as the prefix, the following method would be called in script:

ExtObject_OnNumberChanged()
In JScript the event function would be defined as follows:
function ExtObject_OnNumberChanged(newNumber) { ... }
Any parameters passed by the event will also be passed to the script function.

DisconnectObject() will disconnect the object specified by the pUnk parameter from the scripting engine.

AddNamedObject() will add the IDispatch interface specified by the pDisp parameter to the script engine, allowing it to be accessed by the name provided by the bsName parameter. Again, for example, if I wanted to allow the dispatch interface of the ExtObject to be accessible from my script, I could just use the following code:

pDispExSinkConnector->AddNamedObject(CComBSTR("ExtObject"), pDispExtObj);
The object is now accessible from script as the ExtObject object.

RemoveNamedObject() removed any object previously inserted by AddNamedObject from the scripting engine.

AddTypeLib() will add enumeration types from the provided type library to the scripting engine. This is useful if you need to call methods which require enumeration types as parameters, since you can use the enumeration type, just as you would in a lower level language.

Using the Code

To use the code with IE is straightforward. It's a good idea to sink to the web browser's DWebBrowserEvents2 interface, and use the DocumentComplete notification to setup the CDispExSinkConnector object:

STDMETHOD(OnDocumentComplete)(IDispatch* pDisp, VARIANT* URL)
{ 
    CComPtr<IDispatch> pDispDoc;
    CComQIPtr<IDispatch> pScriptDisp;
    CComQIPtr<IDispatchEx> pDispEx;
    CComQIPtr<IHTMLDocument> pHTMLDoc;
    CComQIPtr<IWebBrowser2> pWebBrowser;
    CDispExSinkConnector *pDispExSinkConnector = NULL;
    
    pWebBrowser = pDisp;
    pWebBrowser->get_Document(&pDispDoc);

    // Get the IDispatchEx interface

    pHTMLDoc = pDispDoc;
    pHTMLDoc->get_Script(&pScriptDisp);
    pDispEx = pScriptDisp;
    
    // Create a new sink connector that will attach to this document

    // object.  This object is reference counted, and IE will properly

    // free it when the document is unloaded

    pDispExSinkConnector = new CDispExSinkConnector();
    pDispExSinkConnector->SetDispEx(pDispEx);

    if(m_pExtObject)
    {
        CComPtr pUnk;
        CComQIPtr pDisp = m_pExtObject;
        pDisp->QueryInterface(IID_IUnknown, (void **) &pUnk);
        
        // Add the named object

        pDispExSinkConnector->AddNamedObject(CComBSTR("ExtObject"), pDisp);
        pDispExSinkConnector->ConnectObject(pUnk, CComBSTR("ExtObject_"), 
            &DIID__IExtObjectEvents, &LIBID_DISPEXOBJECTLib);
    }
            
    return S_OK; 
}

From this point on, you will receive events and be able to access the object from script provided in the HTML page.

History

  • 02/28/2003 - Fixed interface leak in DispExSinkConnector.h
  • 12/08/2002 - Initial version

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here