12 votes

Appeler une fonction C++ depuis un script JavaScript s'exécutant dans un contrôle de navigateur web

J'ai intégré un contrôle de navigateur Web dans mon application c++. Je veux que le javascript s'exécutant dans le contrôle de navigateur Web puisse appeler une fonction/méthode c++.

J'ai trouvé des mentions de trois façons de faire cela :

  1. Implémenter un composant ActiveX qui agit comme intermédiaire. (Détails de mise en œuvre ici : http://blogs.msdn.com/b/nicd/archive/2007/04/18/calling-into-your-bho-from-a-client-script.aspx)
  2. Utiliser window.external. (Discuté également dans le lien ci-dessus, mais aucune implémentation fournie)
  3. Ajouter un objet personnalisé à l'objet window

Je veux opter pour la troisième option, mais je n'ai trouvé aucun exemple fonctionnel sur la manière de le faire. Est-ce que quelqu'un pourrait me montrer comment le faire, ou me donner un lien vers un exemple fonctionnel sur le net quelque part.

Le plus proche d'un exemple que j'ai trouvé est la première réponse d'Igor Tandetnik dans un fil de discussion dans le newsgroup du contrôle de navigateur Web. Mais j'ai peur d'avoir besoin de plus d'aide que cela.

Je suis en train d'intégrer un contrôle IWebBrowser2 et je n'utilise pas MFC, ATL ni WTL.

ÉDIT :

En me basant sur le pseudo-code donné par Igor dans le fil de discussion que j'ai mentionné plus tôt, et le code trouvé dans l'article du codeproject "Créer des tableaux et autres objets JavaScript depuis C++", j'ai produit du code.

void WebForm::AddCustomObject(IDispatch *custObj, std::string name)
{
    IHTMLDocument2 *doc = GetDoc();
    IHTMLWindow2 *win = NULL;
    doc->get_parentWindow(&win);

    if (win == NULL) {
        return;
    }

    IDispatchEx *winEx;
    win->QueryInterface(&winEx);

    if (winEx == NULL) {
        return;
    }

    int lenW = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, name.c_str(), -1, NULL, 0);
    BSTR objName = SysAllocStringLen(0, lenW);
    MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, name.c_str(), -1, objName, lenW);

    DISPID dispid; 
    HRESULT hr = winEx->GetDispID(objName, fdexNameEnsure, &dispid);

    SysFreeString(objName);

    if (FAILED(hr)) {
        return;
    }

    DISPID namedArgs[] = {DISPID_PROPERTYPUT};
    DISPPARAMS params;
    params.rgvarg = new VARIANT[1];
    params.rgvarg[0].pdispVal = custObj;
    params.rgvarg[0].vt = VT_DISPATCH;
    params.rgdispidNamedArgs = namedArgs;
    params.cArgs = 1;
    params.cNamedArgs = 1;

    hr = winEx->InvokeEx(dispid, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYPUT, ¶ms, NULL, NULL, NULL); 

    if (FAILED(hr)) {
        return;
    }
}

Le code ci-dessus s'exécute jusqu'au bout, donc tout semble correct jusqu'à présent.

J'appelle AddCustomObject lorsque je reçois l'événement DWebBrowserEvents2 DISPID_NAVIGATECOMPLETE2 en passant this comme *custObj:

class JSObject : public IDispatch {
private:
    long ref;

public:
    // IUnknown
    virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppv);
    virtual ULONG STDMETHODCALLTYPE AddRef();
    virtual ULONG STDMETHODCALLTYPE Release();

    // IDispatch
    virtual HRESULT STDMETHODCALLTYPE GetTypeInfoCount(UINT *pctinfo);
    virtual HRESULT STDMETHODCALLTYPE GetTypeInfo(UINT iTInfo, LCID lcid,
        ITypeInfo **ppTInfo);
    virtual HRESULT STDMETHODCALLTYPE GetIDsOfNames(REFIID riid,
        LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId);
    virtual HRESULT STDMETHODCALLTYPE Invoke(DISPID dispIdMember, REFIID riid,
        LCID lcid, WORD wFlags, DISPPARAMS *Params, VARIANT *pVarResult,
        EXCEPINFO *pExcepInfo, UINT *puArgErr);
};

Les implémentations notables pourraient être

HRESULT STDMETHODCALLTYPE JSObject::QueryInterface(REFIID riid, void **ppv)
{
    *ppv = NULL;

    if (riid == IID_IUnknown || riid == IID_IDispatch) {
        *ppv = static_cast(this);
    }

    if (*ppv != NULL) {
        AddRef();
        return S_OK;
    }

    return E_NOINTERFACE;
}

et

HRESULT STDMETHODCALLTYPE JSObject::Invoke(DISPID dispIdMember, REFIID riid,
    LCID lcid, WORD wFlags, DISPPARAMS *Params, VARIANT *pVarResult,
    EXCEPINFO *pExcepInfo, UINT *puArgErr)
{
    MessageBox(NULL, "Invoke", "JSObject", MB_OK);
    return DISP_E_MEMBERNOTFOUND;
}

Malheureusement, je ne reçois jamais la boîte de message "Invoke" lorsque j'essaie d'utiliser l'objet "JSObject" depuis le code javascript.

JSObject.randomFunctionName();  // Cela devrait me donner la boîte de message c++ "Invoke"
                                // mais cela ne le fait pas

ÉDIT 2 :

J'ai implémenté GetIDsOfNames de la manière suivante :

HRESULT STDMETHODCALLTYPE JSObject::GetIDsOfNames(REFIID riid,
    LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId)
{
    HRESULT hr = S_OK;

    for (UINT i = 0; i < cNames; i++) {
        std::map::iterator iter = idMap.find(rgszNames[i]);
        if (iter != idMap.end()) {
            rgDispId[i] = iter->second;
        } else {
            rgDispId[i] = DISPID_UNKNOWN;
            hr = DISP_E_UNKNOWNNAME;
        }
    }

    return hr;
}

et voici mon constructeur

JSObject::JSObject() : ref(0)
{
    idMap.insert(std::make_pair(L"execute", DISPID_USER_EXECUTE));
    idMap.insert(std::make_pair(L"writefile", DISPID_USER_WRITEFILE));
    idMap.insert(std::make_pair(L"readfile", DISPID_USER_READFILE));
}

avec les constantes DISPID_USER_* définies en tant que membres de classe privés

class JSObject : public IDispatch {
private:
    static const DISPID DISPID_USER_EXECUTE = DISPID_VALUE + 1;
    static const DISPID DISPID_USER_WRITEFILE = DISPID_VALUE + 2;
    static const DISPID DISPID_USER_READFILE = DISPID_VALUE + 3;

    // ...
};

ÉDIT 3, 4 et 5:

Déplacé vers une question séparée

ÉDIT 6:

J'ai reformaté les modifications "retournant une chaîne" par souci de clarté. De cette façon, je peux accepter la réponse de Georg comme réponse à la question originale.

ÉDIT 7:

J'ai reçu quelques demandes pour un exemple d'implémentation entièrement fonctionnel et autonome. Le voici : https://github.com/Tobbe/CppIEEmbed. N'hésitez pas à le cloner et à l'améliorer si vous le pouvez :)

5voto

Georg Fritzsche Points 59185

Vous devez implémenter GetIDsOfNames() pour faire quelque chose de sensé car cette fonction sera appelée par le code client avant Invoke().
Si vos interfaces se trouvent dans une bibliothèque de types, consultez ici pour un exemple. Si vous préférez utiliser la liaison tardive, vous pouvez utiliser les DISPIDs supérieurs à DISPID_VALUE et inférieurs à 0x80010000 (toutes les valeurs <= 0 et dans la plage 0x80010000 à 0x8001FFFF sont réservées) :

HRESULT GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames, UINT cNames, 
                      LCID lcid, DISPID *rgDispId)
{
    HR hr = S_OK;
    for (UINT i=0; i

``

Notez que les DISPIDs ne sont pas censés changer soudainement, donc par exemple une map statique ou des valeurs constantes devraient être utilisées.

``

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X