0

J'ai un contrôle ActiveX dans une page HTML IE7/8 incorporée qui a l'événement suivant [id(1)] HRESULT MessageReceived([in] BSTR id, [in] BSTR json). Sous Windows, l'événement est enregistré avec OCX.attachEvent("MessageReceived", onMessageReceivedFunc).IE attachEvent sur la balise object provoque une corruption de la mémoire

Le code suivant déclenche l'événement dans la page 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; 
} 

Après avoir permis à gflags.exe avec vérification de l'application, le comportement étrange suivantes se produisent: Après Invoke() qui exécute la fonction de rappel JavaScript, le BSTR de pvars [1] est copié dans pvars [0] pour une raison inconnue !? Le delete [] de pvars provoque un double gratuit de la même chaîne puis qui se termine par une corruption de tas.

Est-ce que quelqu'un a une idée de ce qui se passe ici? Est-ce un bug d'IE ou y at-il un truc dans l'implémentation d'OCX qui me manque?

Si j'utilise la balise comme:

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

... l'étrange opération de copie ne se produit pas.

Le code suivant semble également être correct en raison du fait que l'appelant de Fire_MessageReceived() est responsable de la libération des 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; 
} 

Merci!

Répondre

2

Ce n'est pas un bogue IE. Il y a beaucoup de choses qui me préoccupent ici, alors je vais les énumérer dans l'ordre où je les ai rencontrés.

  1. Pourquoi faites-vous ceci: T* pT = static_cast<T*>(this);? Tu ne devrais jamais avoir à faire ça. Si Lock() et Unlock() sont des méthodes dans votre objet, il suffit de les appeler. Pourquoi téléphonez-vous aux Lock() et Unlock()? Que font-ils? Tous les objets IE COM (c'est-à-dire les objets COM de votre extension) sont STA. Si elles sont à un seul fil, pourquoi faites-vous le verrouillage?
  2. Vous devriez changer cela: int nConnections = m_vec.GetSize(); à ceci: const int nConnections = m_vec.GetSize();, mais cela n'a vraiment aucune incidence sur votre accident.
  3. Ceci est complètement faux: IDispatch* pDispatch = reinterpret_cast<IDispatch*>(sp.p);. Ne lancez pas les objets COM vous-même. Vous devez appeler sp->QueryInterface(IID_IDispatch, (void**)&pDispatch); et vérifier le HRESULT il revient pour le succès. Ensuite, vous n'avez pas besoin de vérifier NULL, car si elle retourne S_OK, le paramètre out est garanti non-NULL. Vous n'avez pas besoin d'appeler VariantClear() sur un CComVariant; le point entier de CComVariant est qu'il le fait pour vous. Même si vous utilisiez un VARIANT standard, vous appelez ici VariantInit() ici (avant de l'utiliser), et non VariantClear() (ce qui est pour après vous avez terminé avec). Ne pas utiliser nouveau et supprimer sur le CComVariant s. Le point entier de CComVariant est qu'il fera la gestion de la mémoire pour vous en interne quand il sort de la portée. L'approche correcte consiste à déclarer un tableau de CComVariant s, similaire à la façon dont vous avez déclaré un tableau basé sur une pile de VARIANT s dans votre deuxième bloc de code. Ensuite, il suffit de se débarrasser de l'instruction de suppression. Je ne suis pas sûr pourquoi votre deuxième exemple ne plante pas, puisque vous appelez delete sur un tableau alloué par la pile. Je suppose que tu es juste chanceux.
  4. Je ne pense pas que vous devriez utiliser CComVariant du tout, puisque (a) vous ne possédez pas les BSTR, ils sont passés, donc probablement quelqu'un d'autre les libère. CComVairant sera SysFreeString() ces bad-boys quand il sort de la portée, et (b) DISPPARAMS ne prend pas VARIANT s, il prend VARIANTARG s et ils ne sont pas la même chose.
  5. Vous devez vérifier le HRESULT qui renvoie Invoke(). En cas d'échec, cela signifie que votre événement n'a pas été déclenché correctement, ce que vous retournez dans varResult.scode n'est donc pas initialisé.
  6. En outre, puisque vous parcourez plusieurs connexions, vous ne renvoyez que le scode du dernier. Si l'un échoue, alors le suivant réussit, qu'est-ce que tu veux vraiment faire? Vous devez comprendre comment gérer cela - je l'ai glissé dans mon exemple ci-dessous.

Voici comment je l'aurais fait:

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

Merci pour vos commentaires approfondis! Le "delete [] pvars;" dans mon deuxième exemple de code était une erreur de copie. Quelle que soit l'implémentation que nous utilisons, la racine du problème est celle avant que la mémoire Invoke() indique: pvars [0] = "a"; pvars [1] = "b"; ... après que la mémoire Invoke() indique ... pvars [0] = "b"; pvars [1] = "b"; ... alors quelqu'un a copié les chaînes dans le tableau. Je suppose que IE le fait. Oui, nous pouvons éviter une corruption de mémoire avec votre code ou mon second code (sans delete [] pvars) en utilisant VARIANT au lieu de CComVariant *. Cependant, les chaînes sont toujours incorrectement copiées uniquement lors de l'utilisation de attachEvent(). – Lars

+0

Il semble que toutes les erreurs proviennent du livre suivant: "Developer's Workshop to COM and ATL 3.0" par Andrew W. Troelsen. Au moins un exemple dans le livre ressemble exactement au code du starter de sujet (et a tous les isses mentionnés ci-dessus). – vond

0

Cela ressemble à un bug connu IE. Ajoutez la clé de contrôle de fonctionnalité FEATURE_LEGACY_DISPPARAMS et définissez sa valeur sur false.

HKEY_LOCAL_MACHINE \ SOFTWARE \ Wow6432Node \ Microsoft \ Internet Explorer \ Main \ FeatureControl \ FEATURE_LEGACY_DISPPARAMS ou HKEY_LOCAL_MACHINE \ SOFTWARE \ Microsoft \ Internet Explorer \ Main \ FeatureControl Nom DWORD: [nom exe] valeur DWORD: 0 (désactiver comportement hérité pour éviter le crash)

Ne se produit que lorsque vous passez plus d'un paramètre et que les paramètres sont des types à supprimer (chaînes par exemple, par opposition aux numéros non affectés).