0

我在嵌入IE7/8 HTML页面中有一个ActiveX控件,该页面有以下事件[id(1)] HRESULT MessageReceived([in] BSTR id, [in] BSTR json)。在Windows上,该事件已注册OCX.attachEvent("MessageReceived", onMessageReceivedFunc)对象标记上的IE attachEvent导致内存损坏

以下代码在HTML页面中触发事件。我启用gflags.exe与应用验证

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; 
} 

后,以下奇怪的行为发生: 调用()正在执行的JavaScript回调,从pvars的BSTR [1]被复制到pvars后[0]为一些未知的原因!? pvars的delete []会导致没有相同字符串的double,然后以堆损坏结束。

有没有人知道这里发生了什么?这是一个IE错误还是我错过了OCX实现中的一个窍门?

如果我使用像标签:

<script for="OCX" event="MessageReceived(id, json)" language="JavaScript" type="text/javascript"> 
    window.onMessageReceivedFunc(windowId, json); 
</script> 

...不会出现奇怪的复制操作。

由于Fire_MessageReceived()的调用者负责释放BSTR,因此下面的代码似乎也没问题。

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; 
} 

谢谢!

回答

2

这不是IE错误。这里有很多关注我的事情,所以我会按照我遇到的顺序列出它们。

  1. 你为什么要这样做:T* pT = static_cast<T*>(this);?你永远不应该这样做。如果Lock()Unlock()是您的对象中的方法,只需调用它们。
  2. 你为什么打电话Lock()Unlock()?他们在做什么?所有的IE COM对象(这意味着你的扩展的COM对象)都是STA。如果他们是单线程的,你为什么要锁定?
  3. 您应该将这个:int nConnections = m_vec.GetSize();改为:const int nConnections = m_vec.GetSize();,但是这对您的崩溃没有任何影响。
  4. 这是完全错误的:IDispatch* pDispatch = reinterpret_cast<IDispatch*>(sp.p);。不要自己投射COM对象。您需要致电sp->QueryInterface(IID_IDispatch, (void**)&pDispatch);并检查HRESULT它会返回成功。那么你不必检查NULL,因为如果它返回S_OK,则out参数保证为非NULL。
  5. 您不必在CComVariant上致电VariantClear(); CComVariant的全部点是它为你做的。即使您使用的是标准VARIANT,您也可以在此处(在使用之前)拨打VariantInit(),而不是VariantClear()(这是在您完成之后)。
  6. 请勿在CComVariant上使用新的和删除。 CComVariant的全部意义在于,当它超出范围时,它会在内部为您执行内存管理。正确的方法是声明一个CComVariant的数组,类似于您在第二个代码块中声明基于堆栈的VARIANT的数组的方式。然后摆脱删除语句。我不确定为什么你第二个例子不会崩溃,因为你在堆栈分配的数组上调用delete。我怀疑你只是幸运。
  7. 我认为你应该根本不应该使用CComVariant,因为(a)你不拥有BSTR s,它们被传入,所以推测其他人正在释放它们。 CComVairantSysFreeString()那些坏男孩当它超出范围,和(b)DISPPARAMS不需要VARIANT s,它需要VARIANTARG s和他们不是一回事。
  8. 您应该检查Invoke()返回的HRESULT。如果失败,这意味着您的活动没有正常启动,因此您在varResult.scode中返回的内容未初始化。
  9. 此外,由于您正在迭代多个连接,因此只返回最后一个的scode。如果失败了,那么下一个成功,你真的想要返回什么?你必须弄清楚如何处理这个问题 - 我在下面的例子中掩盖了它。

这里是我会做:

HRESULT Fire_MessageReceived(BSTR srcWindowId, BSTR json) { 
    CComVariant varResult; 
    VARIANTARG vars[2]; 
    const int nConnections = m_vec.GetSize(); 
    for (int i = 0; i < nConnections; ++i) { 
    Lock(); 
    CComPtr<IUnknown> sp = m_vec.GetAt(nConnectionIndex); 
    Unlock(); 

    IDispatch* pDispatch; 
    HRESULT hr = sp->QueryInterface(IID_IDispatch, (void**)&pDispatch); 
    if (SUCCEEDED(hr)) { 
     pvars[1].vt = VT_BSTR; 
     pvars[1].bstrVal = srcWindowId; 
     pvars[0].vt = VT_BSTR; 
     pvars[0].bstrVal = json; 

     DISPPARAMS disp = { pvars, NULL, ARRAYSIZE(vars), 0 }; 
     hr = pDispatch->Invoke(0x1, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &disp, &varResult, NULL, NULL); 
    } 
    } 

    return (SUCCEEDED(hr) ? varResult.scode : hr); 
} 
+0

感谢您的广泛评论! “删除[] pvars;”在我的第二个代码示例中是一个复制错误。 无论我们使用哪个实施方式中,问题的根源在于,前调用()存储表示: pvars [0] =“A”; pvars [1] =“b”; ...调用后()内存说... pvars [0] =“b”; pvars [1] =“b”; ...所以有人已经复制了数组中的字符串。我想IE是这样做的。 是的,我们可以用VARIANT,而不是CComVariant的避免您的代码或我的第二个代码中的内存破坏(不删除[] pvars)*。但是,仅当使用attachEvent()时,字符串仍然被错误地复制。 – Lars 2010-06-17 09:57:42

+0

看起来所有的错误都来自以下书籍:Andrew W. Troelsen的“COM和ATL 3.0开发者研讨会”。本书中至少有一个例子看起来与主题起始者的代码完全相同(并且具有上面提到的所有主题)。 – vond 2015-01-15 04:53:56

0

这听起来像一个已知的IE漏洞。添加FEATURE_LEGACY_DISPPARAMS功能控制键并将其值设置为false。

HKEY_LOCAL_MACHINE \ SOFTWARE \ Wow6432Node \微软\的Internet Explorer \ MAIN \ FeatureControl \ FEATURE_LEGACY_DISPPARAMS 或 HKEY_LOCAL_MACHINE \ SOFTWARE \微软\的Internet Explorer \ MAIN \ FeatureControl DWORD名:exe文件名] DWORD值:0(禁用传统行为,以避免崩溃)

只有当你传递一个以上的参数和参数是需要被删除,例如(字符串相对于未分配号码)的类型发生了。