对象标记上的IE attachEvent会导致内存损坏

时间:2010-06-16 15:26:37

标签: internet-explorer activex idispatch object-tag connection-points

我在嵌入式IE7 / 8 HTML页面中有一个ActiveX控件,其中包含以下事件[id(1)] HRESULT MessageReceived([in] BSTR id, [in] BSTR json)。在Windows上,事件已在OCX.attachEvent("MessageReceived", onMessageReceivedFunc)注册。

以下代码在HTML页面中触发事件。

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

使用应用程序验证程序启用gflags.exe后,会发生以下奇怪的行为: 在执行JavaScript回调的Invoke()之后,来自pvars [1]的BSTR由于某种未知原因被复制到pvars [0]! pvars的delete []导致双重释放相同的字符串,然后以堆损坏结束。

有人知道这里有什么想法吗?这是一个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 个答案:

答案 0 :(得分: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 param保证为非NULL。
  5. 您无需在VariantClear()上致电CComVariant; CComVariant的全部意义在于它为你做到了。即使您使用的是标准VARIANT,也可以在此处(在使用之前)调用VariantInit(),而不是VariantClear()(这是在您完成之后)。
  6. 请勿在{{1​​}}上使用new和delete。 CComVariant的重点在于,当它超出范围时,它将在内部对你进行内存管理。正确的方法是声明一个CComVariant的数组,类似于在第二个代码块中声明基于堆栈的CComVariant数组的方式。然后摆脱删除声明。我不确定为什么你的第二个例子没有崩溃,因为你在堆栈分配的数组上调用delete。我怀疑你很幸运。
  7. 我认为你根本不应该使用VARIANT,因为(a)你没有拥有CComVariant,它们被传入,所以大概是其他人正在释放它们。当BSTR超出范围时,CComVairant将会SysFreeString()这些坏男孩,(b)DISPPARAMS不会VARIANTVARIANTARG s而且他们不是一回事。
  8. 您应该检查HRESULT返回的Invoke()。如果失败,则表示您的活动未正确触发,因此您在varResult.scode中返回的内容未初始化。
  9. 此外,由于您正在迭代多个连接,因此您只返回最后一个连接的scode。如果一个失败,那么下一个成功,你真的想要回归什么?你必须弄清楚如何处理 - 我在下面的例子中掩饰了它。
  10. 我将如何做到这一点:

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

答案 1 :(得分:0)

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

HKEY_LOCAL_MACHINE \ SOFTWARE \ Wow6432Node \ Microsoft \ Internet Explorer \ Main \ FeatureControl \ FEATURE_LEGACY_DISPPARAMS 要么 HKEY_LOCAL_MACHINE \ SOFTWARE \ Microsoft \ Internet Explorer \ Main \ FeatureControl DWORD名称:[exe名称] DWORD值:0(禁用遗留行为以避免崩溃)

仅当您传递多个参数且参数是需要删除的类型时才会发生(例如,字符串与未分配的数字相对)。