Implementing ActiveX events that can be used in JavaScript

Recently I came across a requirement to create an ATL ActiveX control that could be used from JavaScript in the following manner:

o = new ActiveXObject("Test.MyControl");
o.OnStateChanged = MyEventHandler;

I figured this would be pretty simple -- just implement an ActiveX control with connection points. Unfortunately, it doesn't work. Internet Explorer complains thus:

ie7_object_error 

This is because JavaScript can't sink COM events. This seemed odd to me, because the same approach works when you try to use the XMLHttpRequest object. As it turns out, XMLHttpRequest doesn't implement "events" as COM events at all. They're actually properties that accept IDispatch pointers. JavaScript functions are marshalled to ActiveX as objects that implement IDispatch and having a method named "call".

Simply put, this is what we need to do to enable ActiveX events to be handled by JavaScript using the syntax shown above:

1) Implement a property in the ActiveX control that accepts an IDispatch pointer.

STDMETHODIMP CTestCtrl::putref_OnStateChanged(IDispatch* newVal)
STDMETHODIMP CTestCtrl::get_OnStateChanged(IDispatch** pVal)

2) Get the DISPID of the "call" method by calling GetIDsOfNames like so:

newVal->GetIDsOfNames(IID_NULL, &szMember, 1, LOCALE_SYSTEM_DEFAULT, pDispId))

3) When you want to invoke the event, construct a VARIANTARG containing the parameters you want to pass to the event in reverse order. Add a pointer to your own IDispatch implementation as the last element in the array.

DISPPARAMS dispParams;
VARIANTARG args[2];

VariantInit(&args[0]);
args[0].vt = VT_BSTR;
args[0].bstrVal = ::SysAllocString(L"The event says hello!");

IDispatch* pDisp;
HRESULT hRes = this->QueryInterface(IID_IDispatch, (void**)&pDisp);

VariantInit(&args[1]);
args[1].vt = VT_DISPATCH;
args[1].pdispVal= pDisp;

memset(&dispParams, 0, sizeof(dispParams));
dispParams.rgvarg = args;
dispParams.cArgs = sizeof(args)/sizeof(args[0]);

EXCEPINFO excepinfo; 
UINT uArgErr;

pEventHandler->Invoke(*pDispId, IID_NULL, LOCALE_SYSTEM_DEFAULT, 
    DISPATCH_METHOD, &dispParams, NULL, &excepinfo, &uArgErr);

4) Clean up when you're done by calling VariantClear on both elements of the args array.

5 Comments

  • Dan Abitbol said

    Hi Ragesh, it is a very useful document, however it cannot work with IE7 and vc7. can you please elaborate it a bit more for this purpose?

  • Ragesh Krishna said

    Dan, On the contrary, this approach works perfectly well both in IE6 and IE7 (at least it does on XP; I haven't tested it on Vista). I used VS2005 for development. What kind of trouble are you running into?

  • Dan Abitbol said

    Hi Ragesh, The javascript function is not called at all. I have implemented a simple ATL object and not a control and done the steps you detailed above. The dispId is always 1 when I call the property from the javascript code. Can you please post a sample code?

  • Dan Abitbol said

    Hi Ragesh, Thank you for your help. In fact I just found what was the issue today, on your article you are referring to the pEventHandler, which is actually not documented, I assumed it to be the IUnkown interface of the object, but in fact it should be declared as CComPtr< IDispatch > pEventHandler and assigned at the STDMETHODIMP CTestCtrl::putref_OnStateChanged(IDispatch* newVal) in a way that pEventHandler = newVal. Your code is then working... Regards,

Comments have been disabled for this content.