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);
pHTMLDoc = pDispDoc;
pHTMLDoc->get_Script(&pScriptDisp);
pDispEx = pScriptDisp;
pDispExSinkConnector = new CDispExSinkConnector();
pDispExSinkConnector->SetDispEx(pDispEx);
if(m_pExtObject)
{
CComPtr pUnk;
CComQIPtr pDisp = m_pExtObject;
pDisp->QueryInterface(IID_IUnknown, (void **) &pUnk);
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