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 :
- 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)
- Utiliser window.external. (Discuté également dans le lien ci-dessus, mais aucune implémentation fournie)
- 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 :)