0

我在具有以下事件的嵌入式 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;
 }

谢谢!

4

2 回答 2

2

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

  1. 你为什么这样做:T* pT = static_cast<T*>(this);?你不应该这样做。如果Lock()并且Unlock()是对象中的方法,只需调用它们。
  2. 你为什么打电话Lock()Unlock()?他们在做什么?所有 IE COM 对象(这意味着您的扩展的 COM 对象)都是 STA。如果它们是单线程的,你为什么要锁定?
  3. 您应该将 this: 更改int nConnections = m_vec.GetSize();为 this: 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. 您不必VariantClear()调用CComVariant; 的全部意义CComVariant在于它为您做到了。即使您使用的是标准VARIANT,您也会VariantInit()在这里调用(在使用之前),而不是VariantClear()(在使用完之后)。
  6. 不要在CComVariants 上使用 new 和 delete。的全部意义CComVariant在于,当它超出范围时,它将在内部为您进行内存管理。正确的方法是声明一个CComVariants 数组,类似于您VARIANT在第二个代码块中声明一个基于堆栈的 s 数组的方式。然后只需摆脱删除语句。我不确定为什么您的第二个示例不会崩溃,因为您在堆栈分配的数组上调用 delete 。我怀疑你只是走运。
  7. 我认为你根本不应该使用CComVariant,因为(a)你不拥有BSTRs,它们被传入了,所以大概其他人正在释放它们。 当它超出范围时,那些坏男孩会不会,并且(b)CComVairant不需要s ,它需要s而且它们不是一回事。SysFreeString()DISPPARAMSVARIANTVARIANTARG
  8. 您应该检查HRESULT返回Invoke()的。如果失败,则意味着您的事件没有正确触发,因此您返回的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);
}
于 2010-06-16T18:35:04.690 回答
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(禁用旧行为以避免崩溃)

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

于 2016-05-24T22:48:52.387 回答