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:
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.