从Javascript调用BHO方法?

时间:2012-01-11 07:47:09

标签: internet-explorer activex atl bho ieaddon

我试图从javascript调用我的BHO方法。问题与以下帖子中所述相同:

  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
  4. 第三个链接是另一个关于它的SO帖子,但我不了解需求和代码。此外,共享的工作样本仍然在Windows 7上崩溃,即ie 8和windows vista,即7。

    如果它有助于我的BHO使用ATL用C ++编写。

    我尝试了什么:

    我写了一个非常基本的BHO,并尝试了 Igor Tandetnik 提到的here方法。没有异常生成但是当我在IE中打开以下html文件时它会说对象未定义

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

    问题:

    1. 请说明是否可以从javascript公开和调用BHO方法,或者我是否必须使用activex公开它(在[2]中由 jeffdav 回答)?如果是,那么该怎么做。
    2. 基本上我想扩展window.external,但上面链接[2]中显示的方式使用var x = new ActiveXObject("MySampleATL.MyClass");;调用约定是相同还是不同?
    3. 注意:

      1. 在SO上有一个相关的帖子,提示可以通过在BHO IDL文件中插入此[id(1), helpstring("method DoSomething")] HRESULT DoSomething();来实现。我不确定它是如何完成的,也无法通过谷歌找到任何支持资源。
      2. 我知道这篇文章calling-into-your-bho-from-a-client-script,但没有尝试过,因为它解决了使用ActiveX的问题。
      3. 我避免使用ActiveX的原因主要是出于安全限制。
      4. 修改1

        <小时/> 似乎有一种方法可以扩展window.external。检查this。特别是标题为IDocHostUIHandler::GetExternal: Extending the DOM.的部分现在假设我们的IDispatch接口位于实现IDocHostUIHandler的同一对象上。然后我们可以这样做:

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

        使用此方法的问题是它不会附加到现有的Windows方法,而是替换它们。请告诉我是不是错了。

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

        编辑3

        <小时/> 花了三天后,我想我应该采取ActiveX路径。编写一个基本的activex只是简单易懂的方法,并在所有主要的IE版本上进行测试。我将这个问题保持开放,请参阅Uri的回答中的评论(非常感谢他)。我已经尝试了他的大部分建议(除了4和5)。 I will also suggest you to see the MSDN IDispatcEx sample。如果您找到解决方案,请发帖,如果我找到解决方案,那么我肯定会在这里更新。

        编辑4

        <小时/> 的 See my last comment in URI's post. Issue Resolved.

1 个答案:

答案 0 :(得分:7)

Igor Tandetnik的方法是正确的方法。该帖子的问题是示例代码(至少在我发现它的几页上)是不完整的。我有很多试验和错误,直到我开始工作。 这是我的代码的很大一部分,可以解决这个问题:

假设你有一个CMyBho类,并且想要为Java脚本公开IMyBho自动化对象

班级定义:
您从标准CComObjectRootEx和CComCoClass派生,使其“共同创建”。您有IObjectWithSiteImpl(重用此基类实现的m_spUnkSite)。 IDispatchImpl实现您的自动化对象,IDispatchEventImpl是从浏览器获取通知的接收器:

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

}

接下来,在SetSite方法中,您注册以获取通知。不要忘记调用基类。

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

文档加载完成后,将调用此函数:

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

至于你的其他问题:

  • 显然,我的回答意味着您可以从BHO创建一个自动化对象来编写脚本。您的对象也可以使用新的ActiveXObject进行实例化。在这种情况下,不要忘记告诉IE你的对象是安全的脚本(旁注:让你的BHO安全的脚本。确保恶意网站将无法利用你的BHO)。

  • 我认为window.myBho比window.external.myBho更好。从语义上讲,'external'是mshtml的时候 浏览器控件托管在另一个应用程序中。

希望这会有所帮助。