从 Javascript 调用 BHO 方法?

Posted

技术标签:

【中文标题】从 Javascript 调用 BHO 方法?【英文标题】:Calling BHO method from Javascript? 【发布时间】:2012-02-07 14:33:37 【问题描述】:

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

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

第三个链接 是另一个 SO 帖子谈论它,但我不明白需要和代码。此外,共享的工作示例在带有 ie 8 的 windows 7 和带有 ie 7 的 windows vista 上不断崩溃。

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

我尝试过的:

我编写了一个非常基本的 BHO,并尝试了 Igor Tandetnik 提到的 here 方法。没有生成异常,但是当我在 IE 中打开以下 html 文件时,它会显示 object 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>

问题:

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

注意:

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

编辑 1


似乎有一种方法可以扩展window.external。检查this。特别是标题为IDocHostUIHandler::GetExternal: Extending the DOM.Now 的部分,假设我们的 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 回答中的 cmets(非常感谢他)。我已经尝试了他的大部分建议(除了 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】:

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 更好。从语义上讲,“外部”是 mshtml 浏览器控件托管在另一个应用程序中。

希望这会有所帮助。

【讨论】:

感谢您的回复。我尝试了你的建议,但是当我在我的 javascript 中访问 bho 对象时,它给了我object undefined。请看我更新的答案,我已经包含了 BHO 代码和相应的 javascript。 我已经把params.rgvarg = &amp;varTanduBar; params.rgdispidNamedArgs = null;这两条语句改成了params.rgvarg = &amp;varMyBho;params.rgdispidNamedArgs = NULL;。否则我使用相同的代码。 另外,我试过DISPATCH_PROPERTYPUTREFDISPATCH_PROPERTYPUT。但在这种情况下,两者都不起作用。 我的应用范围很有限。它在任何给定页面上插入 jquery 和 jqueryui 库。然后将其用于增强现有应用程序的外观。将 html 文本保存在固定位置并始终作为文本需要与 js 和 bho 交互。您所说的问题是非常真实的,并且还有与 activex 相关的安全角度。这就是我想使用 bho 的原因。我创建了简单的裸骨 bho 应用程序来测试这一点。请在code.google.com/p/simple-atl-bho/downloads/list 找到它。谢谢。 @uri:谢谢。终于让它工作了,即从 JS 调用 BHO 而没有 ActiveX。至少适用于 IE8 和 IE7。虽然不确定ie9及以上。这只是我的一个愚蠢的错误!忘记在idl中公开方法。虽然DISPATCH_PROPERTYPUTREF 对我不起作用,但我尝试过DISPATCH_PROPERTYPUT 并且它起作用了。感谢您的宝贵时间。

以上是关于从 Javascript 调用 BHO 方法?的主要内容,如果未能解决你的问题,请参考以下文章

如何用普通 C++ 编写一个执行我的 javascript 的 IE BHO

从 BHO ( C++ ) 在 CAxWindow 上向 JS 公开方法

IE BHO - 为页面加载调用 DISPID_FILEDOWNLOAD?

从 javascript 适配器调用 java 代码时出错

如何从母版页中的控件调用 javascript 函数?

覆盖默认浏览器alert()方法