2012-01-11 17 views
10

Estoy tratando de llamar a mi método BHO desde el javascript. El problema es el mismo que se indica en los siguientes mensajes:¿Llamando al método BHO desde Javascript?

  1. Call BHO from Javascript function
  2. http://social.msdn.microsoft.com/Forums/en-US/ieextensiondevelopment/thread/91d4076e-4795-4d9e-9b07-5b9c9eca62fb/
  3. Calling C++ function from JavaScript script running in a web browser control

tercer enlace es otro SO post hablando sobre ello, pero no entiendo la necesidad y el código. Además, la muestra de trabajo compartida sigue fallando en Windows 7, es decir, 8 y Windows Vista, es decir, 7

Si ayuda mi BHO está escrita en C++ utilizando ATL.

Lo que he intentado:

He escrito un BHO muy básico y probado el método como se ha mencionado here por Igor Tandetnik. No se genera ninguna excepción, pero cuando abro el siguiente archivo html en IE dice objeto indefinido.

<html> 
    <head> 
     <script language='javascript'> 
      function call_external(){ 
       try{ 
       alert(window.external.TestScript); 
       //JQueryTest.HelloJquery('a'); 
       }catch(err){ 
        alert(err.description); 
       } 
      } 
     </script> 
    </head> 
    <body id='bodyid' onload="call_external();"> 
     <center><div><span>Hello jQuery!!</span></div></center> 
    </boay> 
</html> 

Pregunta:

  1. Por favor aclarar si es posible exponer y llamar al método BHO desde JavaScript o tengo para exponerlo utilizando un ActiveX (como respondida por jeffdav en [2])? Si es así, ¿cómo hacerlo?
  2. Básicamente quiero extender el window.external pero la forma que se muestra en el enlace de arriba [2] usa var x = new ActiveXObject("MySampleATL.MyClass");; ¿Ambas convenciones son iguales o diferentes?

Nota:

  1. Hay un post relacionado en SO que da indicio de que es posible a través de la inserción de este [id(1), helpstring("method DoSomething")] HRESULT DoSomething(); en el archivo IDL BHO. No estoy seguro de cómo se hizo y no pude encontrar ningún recurso de apoyo a través de google.
  2. Conozco esta publicación calling-into-your-bho-from-a-client-script, pero no la he probado, ya que está solucionando el problema con ActiveX.
  3. Mi razón para evitar ActiveX se debe principalmente a las restricciones de seguridad.

Editar 1


Parece que hay una manera de extender la window.external. Compruebe this. Especialmente la sección titulada IDocHostUIHandler::GetExternal: Extending the DOM. Ahora suponiendo que nuestra interfaz IDispatch está en el mismo objeto que implementa IDocHostUIHandler.Entonces podemos hacer algo como esto:

HRESULT CBrowserHost::GetExternal(IDispatch **ppDispatch) 
{ 
    *ppDispatch = this; 
    return S_OK; 
} 

El problema con este enfoque es que no va a anexar a los métodos existentes de Windows, sino más bien reemplazarlos. Dime si estoy equivocado.

Editar 2


The BHO Class:

class ATL_NO_VTABLE CTestScript : 
    public CComObjectRootEx<CComSingleThreadModel>, 
    public CComCoClass<CTestScript, &CLSID_TestScript>, 
    public IObjectWithSiteImpl<CTestScript>, 
    public IDispatchImpl<ITestScript, &IID_ITestScript, &LIBID_TestBHOLib, /*wMajor =*/ 1, /*wMinor =*/ 0>, 
    public IDispEventImpl<1, CTestScript, &DIID_DWebBrowserEvents2, &LIBID_SHDocVw, 1, 1> 
{ 
public: 
    CTestScript() 
    { 
    } 

DECLARE_REGISTRY_RESOURCEID(IDR_TESTSCRIPT) 

DECLARE_NOT_AGGREGATABLE(CTestScript) 

BEGIN_COM_MAP(CTestScript) 
    COM_INTERFACE_ENTRY(ITestScript) 
    COM_INTERFACE_ENTRY(IDispatch) 
    COM_INTERFACE_ENTRY(IObjectWithSite) 
END_COM_MAP() 



    DECLARE_PROTECT_FINAL_CONSTRUCT() 

    HRESULT FinalConstruct() 
    { 
     return S_OK; 
    } 

    void FinalRelease() 
    { 
    } 

public: 
    BEGIN_SINK_MAP(CTestScript) 
     SINK_ENTRY_EX(1, DIID_DWebBrowserEvents2, DISPID_DOCUMENTCOMPLETE, OnDocumentComplete) 
     //SINK_ENTRY_EX(1, DIID_DWebBrowserEvents2, DISPID_NAVIGATECOMPLETE2, OnNavigationComplete) 
    END_SINK_MAP() 

    void STDMETHODCALLTYPE OnDocumentComplete(IDispatch *pDisp, VARIANT *pvarURL); 
    //void STDMETHODCALLTYPE OnNavigationComplete(IDispatch *pDisp, VARIANT *pvarURL); 

    STDMETHOD(SetSite)(IUnknown *pUnkSite); 

    HRESULT STDMETHODCALLTYPE DoSomething(){ 
     ::MessageBox(NULL, L"Hello", L"World", MB_OK); 
     return S_OK; 
    } 
public: 

//private: 
    // InstallBHOMethod(); 

private: 
    CComPtr<IWebBrowser2> m_spWebBrowser; 
    BOOL m_fAdvised; 
}; 

// TestScript.cpp : Implementation of CTestScript

#include "stdafx.h" 
#include "TestScript.h" 


// CTestScript 

STDMETHODIMP CTestScript::SetSite(IUnknown* pUnkSite) 
{ 
    if (pUnkSite != NULL) 
    { 
     HRESULT hr = pUnkSite->QueryInterface(IID_IWebBrowser2, (void **)&m_spWebBrowser); 
     if (SUCCEEDED(hr)) 
     { 
      hr = DispEventAdvise(m_spWebBrowser); 
      if (SUCCEEDED(hr)) 
      { 
       m_fAdvised = TRUE;    
      } 
     } 
    }else 
    { 
     if (m_fAdvised) 
     { 
      DispEventUnadvise(m_spWebBrowser); 
      m_fAdvised = FALSE; 
     } 
     m_spWebBrowser.Release(); 
    } 
    return IObjectWithSiteImpl<CTestScript>::SetSite(pUnkSite); 
} 

void STDMETHODCALLTYPE CTestScript::OnDocumentComplete(IDispatch *pDisp, VARIANT *pvarURL) 
{ 
     CComPtr<IDispatch> dispDoc; 
     CComPtr<IHTMLDocument2> ifDoc; 
     CComPtr<IHTMLWindow2> ifWnd; 
     CComPtr<IDispatchEx> dispxWnd; 

     HRESULT hr = m_spWebBrowser->get_Document(&dispDoc); 
     hr = dispDoc.QueryInterface(&ifDoc);  
     hr = ifDoc->get_parentWindow(&ifWnd); 
     hr = ifWnd.QueryInterface(&dispxWnd); 

     // now ... be careful. Do exactly as described here. Very easy to make mistakes 
     CComBSTR propName(L"myBho"); 
     DISPID dispid; 
     hr = dispxWnd->GetDispID(propName, fdexNameEnsure, &dispid); 

     CComVariant varMyBho((IDispatch*)this); 
     DISPPARAMS params; 
     params.cArgs = 1; 
     params.cNamedArgs = 0; 
     params.rgvarg = &varMyBho;    
     params.rgdispidNamedArgs = NULL; 
     hr = dispxWnd->Invoke(dispid, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYPUT, 
      &params, NULL, NULL, NULL); 

} 

The Javascript:

<script language='javascript'> 
      function call_external(){ 
       try{ 
       alert(window.ITestScript); 
       }catch(err){ 
        alert(err.description); 
       } 
      } 
     </script> 

Datos 3


Después de pasar tres días en esto, creo que debería tomar el camino de ActiveX. Escribir un activex básico es fácil, escrito y probado en todas las principales versiones de IE. Dejo esta pregunta abierta, por favor vea los comentarios en la respuesta de Uri (muchas gracias a él). He intentado la mayoría de sus sugerencias (excepto 4 y 5). I will also suggest you to see the MSDN IDispatcEx sample. Si encuentra una solución, por favor publique, si encuentro una solución, definitivamente la actualizaré aquí.

Editar 4


See my last comment in URI's post. Issue Resolved.

Respuesta

7

método de Igor Tandetnik es el enfoque correcto. El problema con la publicación es que el código de muestra (al menos en las pocas páginas que vi) es que no estaba completo. Tuve muchas pruebas y errores hasta que lo hice funcionar. Aquí es buena parte de mi código que hace el truco:

Digamos que tienes una clase CMyBho, y que desea exponer IMyBho objeto de automatización para los scripts de Java

definición de clase:
que se derivan de la CComObjectRootEx norma y CComCoClass para que sea 'cocreador'. Tienes IObjectWithSiteImpl (reutiliza el m_spUnkSite implementado por esta clase base). IDispatchImpl implementa el objeto de automatización, y IDispatchEventImpl es el fregadero para recibir notificaciones desde el navegador:

class ATL_NO_VTABLE CMyBho 
    : public CComObjectRootEx<CComSingleThreadModel> 
    , public CComCoClass<CMyBho, &CLSID_MyBho> 
    , public IObjectWithSiteImpl<CMyBho> 
    , public IDispatchImpl<IMyBho, &IID_IMyBho, &LIBID_MyBhoLib, 1, 0> 
    , IDispatchEventImpl<1, CMyBho, &DIID_DWebBrowserEvents2, &LIBID_SHDocVw, 1, 1> 
{ 
    ... 

public: 
    BEGIN_COM_MAP(CMyBho) 
     COM_INTERFACE_ENTRY(IMyBho) 
     COM_INTERFACE_ENTRY(IDispatch) 
     COM_INTERFACE_ENTRY(IObjectWithSite) 
    END_COM_MAP() 

    ... 

    BEGIN_SINK_MAP(CMyBho) 
     SINK_ENTRY_EX(1, DIID_DWebBrowserEvents2, DISPID_DOCUMENTCOMPLETE, OnDocComplete) 
    END_SINK_MAP() 

    ... 

private: 
    CComPtr<IWebBrowser2> m_ifbrz;   // pointer to the hosting browser 

} 

A continuación, el método SetSite, donde se registra para recibir notificaciones. No te olvides de llamar a la clase base.

STDMETHODIMP CMyBho::SetSite(IUnknown* unkSite) 
{ 
    ... 
    hr = IObjectWithSiteImpl::SetSite(unkSite); 
    if(unkSite) { 
     ... 
     // advise to browser event. 
     CComPtr<IServiceProvider> ifsp; 
     hr = m_spUnkSite.QueryInterface(&ifsp); 
     hr = ifsp->QueryService(SID_SwebBrowserApp, IID_IWebBrowser2, &m_ifbrz); 
     hr = DispEventAdvise(m_ifbrz); 
    } 
    else { 
     // release various resources (m_ifbrz will be released automatically by its dtor) 
     ... 
    } 
... 
} 

Cuando la carga documento sea completa, esta función se llamará:

void STDMETHODCALLTYPE CMyBho::onDocComplete(IDispatch* dispBrz, VARIANT* pvarUrl) 
{ 
    CComPtr<IDispatch> dispDoc; 
    CComPtr<IHTMLDocument2> ifDoc; 
    CComPtr<IHTMLWindow2> ifWnd; 
    CComPtr<IDispatchEx> dispxWnd; 

    hr = m_ifbrz->get_Document(&dispDoc); 
    hr = dispDoc.QueryInterface(&ifDoc);  
    hr = ifDoc->get_parentWindow(&ifWnd); 
    hr = ifWnd.QueryInterface(&dispxWnd); 

    // now ... be careful. Do exactly as described here. Very easy to make mistakes 
    CComBSTR propName(L"myBho"); 
    DISPID dispid; 
    hr = dispxWnd->GetDispID(propName, fdexNameEnsure, &dispid); 

    CComVariant varMyBho((IDispatch*)this); 
    DISPPARAMS params; 
    params.cArgs = 1; 
    params.cNamedArgs = 0; 
    params.rgvarg = &varMyBho;    
    params.rgdispidNamedArgs = NULL; 
    hr = dispxWnd->Invoke(dispid, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYPUTREF, 
          &params, NULL, NULL, NULL); 
} 

En cuanto a sus otras preguntas:

  • Evidentemente, mi respuesta implica que usted puede hace una automatización objeto disponible para secuencias de comandos de su BHO. También es posible que su objeto se instanciará con un nuevo ActiveXObject.En ese caso, no olvide indicarle a IE que su objeto es seguro para secuencias de comandos (nota al margen: haga que su BHO sea segura para la creación de scripts. Asegúrese de que el sitio web malicioso no pueda explotar su BHO).

  • Creo que window.myBho es un lugar mejor que window.external.myBho. Semánticamente, 'externo' es cuando el control del navegador mshtml se aloja dentro de otra aplicación.

Hope this help.

+0

Gracias por su respuesta. Intenté su sugerencia, pero cuando estoy accediendo al objeto bho en mi javascript, me está dando 'object undefined'. Por favor, mira mi respuesta actualizada, he incluido el código BHO y el javascript correspondiente. – Favonius

+0

He cambiado estas dos sentencias 'params.rgvarg = & varTanduBar; params.rgdispidNamedArgs = null; 'a este' params.rgvarg = & varMyBho; params.rgdispidNamedArgs = NULL; '. De lo contrario, he usado el mismo código. – Favonius

+0

Además, he intentado 'DISPATCH_PROPERTYPUTREF' y' DISPATCH_PROPERTYPUT'. Pero en este caso, ambos no están funcionando. – Favonius

Cuestiones relacionadas