【问题标题】:IE attachEvent on object tag causes memory corruption对象标签上的 IE attachEvent 导致内存损坏
【发布时间】:2010-06-16 15:26:37
【问题描述】:

我在嵌入式 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;
 }

谢谢!

【问题讨论】:

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


    【解决方案1】:

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

    1. 你为什么这样做:T* pT = static_cast&lt;T*&gt;(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&lt;IDispatch*&gt;(sp.p);。不要自己投射 COM 对象。您需要调用sp-&gt;QueryInterface(IID_IDispatch, (void**)&amp;pDispatch); 并检查它返回的HRESULT 是否成功。然后您不必检查 NULL,因为如果它返回 S_OK,则 out 参数保证为非 NULL。
    5. 您不必在CComVariant 上致电VariantClear()CComVariant 的全部意义在于它为你做这件事。即使您使用的是标准的VARIANT,您也应该在此处调用VariantInit()(在使用之前),而不是VariantClear()(在使用完之后)。
    6. 不要在CComVariants 上使用 new 和 delete。 CComVariant 的全部意义在于,当它超出范围时,它将在内部为您进行内存管理。正确的方法是声明一个CComVariants 数组,类似于在第二个代码块中声明一个基于堆栈的VARIANTs 数组的方式。然后只需摆脱删除语句。我不确定为什么您的第二个示例不会崩溃,因为您在堆栈分配的数组上调用 delete 。我怀疑你只是走运。
    7. 我认为你根本不应该使用CComVariant,因为(a)你不拥有BSTRs,它们被传入了,所以大概是其他人在释放它们。 CComVairant 将在超出范围时将SysFreeString() 那些坏男孩,并且(b)DISPPARAMS 不需要VARIANTs,它需要VARIANTARGs,它们不是一回事。
    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);
    }
    

    【讨论】:

    • 感谢您广泛的 cmets! “删除[] pvars;”在我的第二个代码示例中是一个复制错误。不管我们使用哪种实现,问题的根源是在 Invoke() 之前内存说: pvars[0] = "a"; pvars[1] = "b"; ... 在 Invoke() 内存说 ... pvars[0] = "b"; pvars[1] = "b"; ...所以有人复制了数组中的字符串。我想 IE 正在这样做。是的,我们可以使用 VARIANT 而不是 CComVariant* 来避免您的代码或我的第二个代码(没有 delete[] pvars)造成内存损坏。然而,只有在使用 attachEvent() 时,字符串仍然会被错误地复制。
    • 所有错误似乎都来自以下书籍:Andrew W. Troelsen 的“Developer's Workshop to COM and ATL 3.0”。书中至少有一个示例看起来与主题启动器的代码完全相同(并且具有上述所有问题)。
    【解决方案2】:

    这听起来像是一个已知的 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(禁用旧行为以避免崩溃)

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-02-09
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多