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(禁用傳統行爲,以避免崩潰)

只有當你傳遞一個以上的參數和參數是需要被刪除,例如(字符串相對於未分配號碼)的類型發生了。