I've an ActiveX Control within an embedded IE7/8 HTML page that has the following event [id(1)] HRESULT MessageReceived([in] BSTR id, [in] BSTR json). On Windows the event is registered with OCX.attachEvent("MessageReceived", onMessageReceivedFunc).
Following code fires the event in the HTML page.
HRESULT Fire_MessageReceived(BSTR id, BSTR json)
{
CComVariant varResult;
T* pT = static_cast<T*>(this);
int nConnectionIndex;
CComVariant* pvars = new CComVariant[2];
int nConnections = m_vec.GetSize();
for (nConnectionIndex = 0; nConnectionIndex < nConnections; nConnectionIndex++)
{
pT->Lock();
CComPtr<IUnknown> sp = m_vec.GetAt(nConnectionIndex);
pT->Unlock();
IDispatch* pDispatch = reinterpret_cast<IDispatch*>(sp.p);
if (pDispatch != NULL)
{
VariantClear(&varResult);
pvars[1] = id;
pvars[0] = json;
DISPPARAMS disp = { pvars, NULL, 2, 0 };
pDispatch->Invoke(0x1, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &disp, &varResult, NULL, NULL);
}
}
delete[] pvars; // -> Memory Corruption here!
return varResult.scode;
}
After I enabled gflags.exe with application verifier, the following strange behaviour occur: After Invoke() that is executing the JavaScript callback, the BSTR from pvars[1] is copied to pvars[0] for some unknown reason!? The delete[] of pvars causes a double free of the same string then which ends in a heap corruption.
Does anybody has an idea whats going on here? Is this a IE bug or is there a trick within the OCX Implementation that I'm missing?
If I use the tag like:
<script for="OCX" event="MessageReceived(id, json)" language="JavaScript" type="text/javascript">
window.onMessageReceivedFunc(windowId, json);
</script>
... the strange copy operation does not occur.
The following code also seem to be ok due to the fact that the caller of Fire_MessageReceived() is responsible for freeing the BSTRs.
HRESULT Fire_MessageReceived(BSTR srcWindowId, BSTR json)
{
CComVariant varResult;
T* pT = static_cast<T*>(this);
int nConnectionIndex;
VARIANT pvars[2];
int nConnections = m_vec.GetSize();
for (nConnectionIndex = 0; nConnectionIndex < nConnections; nConnectionIndex++)
{
pT->Lock();
CComPtr<IUnknown> sp = m_vec.GetAt(nConnectionIndex);
pT->Unlock();
IDispatch* pDispatch = reinterpret_cast<IDispatch*>(sp.p);
if (pDispatch != NULL)
{
VariantClear(&varResult);
pvars[1].vt = VT_BSTR;
pvars[1].bstrVal = srcWindowId;
pvars[0].vt = VT_BSTR;
pvars[0].bstrVal = json;
DISPPARAMS disp = { pvars, NULL, 2, 0 };
pDispatch->Invoke(0x1, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &disp, &varResult, NULL, NULL);
}
}
delete[] pvars;
return varResult.scode;
}
Thanks!
This is not an IE bug. There are a lot of things going on here that concern me, so I'll list them in the order I encountered them.
T* pT = static_cast<T*>(this);? You shouldn't ever have to do this. IfLock()andUnlock()are methods in your object, just call them.Lock()andUnlock()? What do they do? All IE COM objects (which means your extension's COM objects) are STA. If they're single threaded, why are you doing locking?int nConnections = m_vec.GetSize();to this:const int nConnections = m_vec.GetSize();, but this really doesn't have any bearing on your crash.IDispatch* pDispatch = reinterpret_cast<IDispatch*>(sp.p);. Don't cast COM objects yourself. You need to callsp->QueryInterface(IID_IDispatch, (void**)&pDispatch);and check theHRESULTit returns for success. Then you don't have to check for NULL, since if it returns S_OK the out param is guaranteed to be non-NULL.VariantClear()on aCComVariant; the whole point ofCComVariantis that it does it for you. Even if you were using a standardVARIANT, you would callVariantInit()here (before you use it), notVariantClear()(which is for after you're done with it).CComVariants. The whole point ofCComVariantis that it will do memory management for you internally when it goes out of scope. The correct approach is to declare an array ofCComVariants, similar to the way you declared a stack-based array ofVARIANTs in your second code block. Then just get rid of the delete statement. I'm not sure why you're second example doesn't crash, since you're calling delete on a stack-allocated array. I suspect you're just getting lucky.CComVariantat all, since (a) you don't own theBSTRs, they're passed in, so presumably someone else is freeing them.CComVairantwillSysFreeString()those bad-boys when it goes out of scope, and (b)DISPPARAMSdoesn't takeVARIANTs, it takesVARIANTARGs and they're not the same thing.HRESULTthatInvoke()returns. If it failed, that means your event didn't properly fire, so what you return invarResult.scodeis uninitialized.scodeof the last one. If one fails, then the next one succeeds, what do you really want to return? You have to figure out how to handle that—I've glossed it over in my example below.Here is how I would have done it: