2012-01-11 8 views
10

Próbuję wywołać moją metodę BHO z javascript. Problem jest taki sam jak podano w następujących stanowisk:Wywołanie metody BHO z 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

trzecie ogniwo to kolejny SO pisać o tym mówić, ale ja nie rozumiem potrzeba i kod. Również udostępniona robocza próbka ulega awarii w systemie Windows 7, np. 8 i Windows Vista z np. 7.

Jeśli to pomaga, mój BHO jest napisany w C++ przy użyciu ATL.

Co próbowałem:

Napisałem bardzo prosty BHO i próbował podejście jak wspomniano here przez Igor Tandetnik. Nie ma generowanego wyjątku, ale gdy otworzę następujący plik html w IE, oznacza to, że obiekt jest niezdefiniowany undefined.

<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> 

Pytanie:

  1. Proszę wyjaśnić, czy to możliwe, aby odsłonić i wywołać metodę BHO z javascript czy muszę wystawiać go za pomocą ActiveX (jak odpowiedzieć jeffdav w [2])? Jeśli tak, to jak to zrobić.
  2. Zasadniczo chcę przedłużyć window.external, ale sposób pokazany w powyższym łączu [2] używa var x = new ActiveXObject("MySampleATL.MyClass");; Czy obie konwencje są takie same czy różne?

Uwaga:

  1. Jest związane post tak, to które daje wskazówkę, że jest możliwe poprzez wstawienie tej [id(1), helpstring("method DoSomething")] HRESULT DoSomething(); w pliku BHO IDL. Nie jestem pewien, jak to się stało i nie mogłem znaleźć żadnych zasobów pomocniczych za pośrednictwem google.
  2. Jestem świadomy tego posta calling-into-your-bho-from-a-client-script, ale nie próbowałem go, ponieważ rozwiązuje problem za pomocą ActiveX.
  3. Mój powód, aby unikać ActiveX wynika przede wszystkim z ograniczeń bezpieczeństwa.

Edycja 1


Wydaje się, że jest to sposób na przedłużenie window.external. Sprawdź this. Zwłaszcza sekcja zatytułowana IDocHostUIHandler::GetExternal: Extending the DOM. Teraz zakładając, że nasz interfejs IDispatch znajduje się na tym samym obiekcie, który implementuje IDocHostUIHandler.Wtedy możemy zrobić coś takiego:

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

problemu z tego podejścia jest to, że nie dołączy do istniejących metod Windows, ale raczej je zastąpić. Powiedz, czy się mylę.

Edycja 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> 

Edycja 3


Po spędzeniu trzech dni na to, myślę, że powinienem wziąć ścieżkę ActiveX. Napisanie podstawowego ActiveX jest po prostu proste, napisane i przetestowane na wszystkich głównych wydaniach IE. Pozostawiam to pytanie jako otwarte, proszę zobaczyć komentarze w odpowiedzi Uri (wielkie dzięki dla niego). Próbowałem większość jego sugestii (z wyjątkiem 4 i 5). I will also suggest you to see the MSDN IDispatcEx sample. Jeśli znajdziesz rozwiązanie, proszę napisz, jeśli znajdę rozwiązanie, to na pewno zaktualizuję tutaj.

Edycja 4


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

Odpowiedz

7

metoda Igora Tandetnik jest właściwe podejście. Problem z postem polega na tym, że przykładowy kod (przynajmniej na kilku stronach, które zauważyłem) jest taki, że nie był kompletny. Miałem wiele prób i błędów, dopóki nie udało mi się go uruchomić. Oto dobry kawał mojego kodu, który załatwia sprawę:

że masz klasę CMyBho i chcesz narazić IMyBho obiekt automatyzacji dla skryptów Java

definicja klasy:
można czerpać ze standardowego CComObjectRootEx i CComCoClass, aby był "co creatble". Masz IObjectWithSiteImpl (użyj ponownie m_spUnkSite zaimplementowanego przez tę klasę bazową). IDispatchImpl realizuje swój obiekt automatyzacji i IDispatchEventImpl jest zlew, aby uzyskać informację z przeglądarki:

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 

} 

Następnie metoda SetSite, gdzie można zarejestrować się dostać powiadomienie. Nie zapomnij zadzwonić do klasy bazowej.

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) 
     ... 
    } 
... 
} 

Kiedy obciążenie dokument jest kompletny, ta funkcja zostanie wywołana:

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); 
} 

Jak dla innych pytań:

  • Widocznie moja odpowiedź sugeruje, że można czyni automatyzację obiekt dostępny do skryptowania z twojego BHO. Możliwe jest również, że obiekt zostanie utworzony za pomocą nowego obiektu ActiveXObject.W takim przypadku nie zapomnij powiedzieć IE, że twój obiekt jest bezpieczny dla skryptów (uwaga strony: spraw, aby Twój BHO był bezpieczny dla skryptów, upewnij się, że złośliwa strona internetowa nie będzie w stanie wykorzystać twojego BHO).

  • Myślę, że window.myBho jest lepszym miejscem niż window.external.myBho. Semantycznie "zewnętrzne" ma miejsce wtedy, gdy kontrolka przeglądarki mshtml jest hostowana w innej aplikacji.

Mam nadzieję, że to pomogło.

+0

Dziękuję za odpowiedź. Próbowałem twojej sugestii, ale kiedy mam dostęp do obiektu bho w moim javascript, to daje mi 'object undefined'. Zobacz moją zaktualizowaną odpowiedź, umieściłem kod BHO i odpowiedni javascript. – Favonius

+0

Zmieniłem te dwie instrukcje 'params.rgvarg = & varTanduBar; params.rgdispidNamedArgs = null; 'to this' params.rgvarg = & varMyBho; params.rgdispidNamedArgs = NULL; '. W przeciwnym razie użyłem tego samego kodu. – Favonius

+0

Również próbowałem 'DISPATCH_PROPERTYPUTREF' i' DISPATCH_PROPERTYPUT'. Ale w tym przypadku oba nie działają. – Favonius

Powiązane problemy