Вызов метода BHO из Javascript?

Я пытаюсь вызвать свой метод BHO из javascript. Проблема такая же, как указано в следующих сообщениях:

  1. Вызов BHO из функции Javascript
  2. http://social.msdn.microsoft.com/Forums/en-US/ieextensiondevelopment/thread/91d4076e-4795-4d9e-9b07-5b9c9eca62fb/
  3. Вызов функции C ++ из сценария JavaScript, выполняющегося в элемент управления веб-браузера

Третья ссылка - это еще одно сообщение SO, в котором говорится об этом, но я не понимал необходимости и кода. Также общий рабочий образец продолжает давать сбой в Windows 7 с IE 8 и Windows Vista с IE 7.

Если это поможет, мой BHO написан на C ++ с использованием ATL.

Что я пробовал:

Я написал очень простой BHO и попробовал упомянутый подход здесь, автор: Игорь Тандетник. Исключения не создается, но когда я открываю следующий html-файл в IE, появляется сообщение объект не определен.

<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. Уточните, можно ли раскрыть и вызвать метод BHO из javascript или мне нужно раскрывать его с помощью activex (как ответил jeffdav в [2])? Если да, то как это сделать.
  2. В основном я хочу расширить window.external, но способ, показанный в приведенной выше ссылке [2] использует var x = new ActiveXObject("MySampleATL.MyClass");; Оба соглашения о вызовах одинаковые или разные?

Примечание.

  1. На SO есть связанный пост, который дает подсказку, что это возможно, вставив этот [id(1), helpstring("method DoSomething")] HRESULT DoSomething(); в файл IDL BHO. Я не уверен, как это было сделано, и не смог найти вспомогательный ресурс через Google.
  2. Мне известно об этом сообщении call-into-your-bho-from-a-client-script, но не пробовал, поскольку он решает проблему с помощью ActiveX.
  3. Моя причина избегать ActiveX в первую очередь связана с ограничениями безопасности.

Изменить 1


Похоже, есть способ расширить window.external. Проверьте это. В частности, в разделе под названием 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. Я оставляю этот вопрос открытым, см. Комментарии в ответе Ури (большое ему спасибо). Я испробовал большинство его предложений (кроме 4 и 5). I will also suggest you to see the MSDN IDispatcEx sample. Если вы найдете решение, напишите, если я найду решение, я обязательно обновлю его здесь.

Изменить 4


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


person Favonius    schedule 11.01.2012    source источник


Ответы (1)


Метод Игоря Тандетника - правильный подход. Проблема с сообщением в том, что образец кода (по крайней мере, на нескольких страницах, которые я заметил) не является полным. У меня было много проб и ошибок, пока он не заработал. Вот хороший фрагмент моего кода, который помогает:

Допустим, у вас есть класс CMyBho, и вы хотите предоставить объект автоматизации IMyBho для сценариев Java.

Определение класса:
Вы унаследованы от стандартных 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 размещается в другом приложении.

Надеюсь, это помогло.

person Uri    schedule 11.01.2012
comment
Спасибо за ответ. Я попробовал ваше предложение, но когда я обращаюсь к объекту bho в своем javascript, он дает мне object undefined. См. Мой обновленный ответ, я включил код BHO и соответствующий javascript. - person Favonius; 11.01.2012
comment
Я заменил эти два утверждения params.rgvarg = &varTanduBar; params.rgdispidNamedArgs = null; на это params.rgvarg = &varMyBho;params.rgdispidNamedArgs = NULL;. В противном случае я использовал тот же код. - person Favonius; 11.01.2012
comment
Также я пробовал DISPATCH_PROPERTYPUTREF и DISPATCH_PROPERTYPUT. Но в этом случае оба не работают. - person Favonius; 11.01.2012
comment
Да, «TanduBar» взят из моего собственного кода. Прости. Я думаю, у вас проблема со сроками. Я подозреваю, что когда JavaScript пытается получить доступ к window.myBho, записи еще нет. Пожалуйста, попробуйте следующее. Измените свой HTML так, чтобы он реагировал на нажатие кнопки, после загрузки страницы (документа). Добавьте следующую кнопку: ‹button onclick = call_external () /› Если это сработает для вас, тогда нам нужно настроить OM окна раньше (если вам нужен объект раньше). Сообщите мне, работает ли кнопка, и я поищу для вас лучшее мероприятие. - person Uri; 11.01.2012
comment
Спасибо. Я пробовал использовать кнопку, но все равно получаю window.ITestScript is null or not an object. Надеюсь, я правильно ссылаюсь на BHO в своем JS. - person Favonius; 11.01.2012
comment
Это очень странно, потому что у меня работал точно такой же код. Не выполняйте DISPATCH_PROPERTYPUT - это не сработает, если свойство является объектом автоматизации. Вот несколько вещей, которые можно попробовать: 1) Откройте консоль разработчика F12 и попробуйте перечислить все свойства окна. 2) Давайте поместим фиктивный params.rgdispidNamedArgs (хотя cNamedArgs остается 0, просто определите фиктивный DISPID и params.rgdispidNamedArgs = 3) Если все еще не работает, напишите код после dispxExternal- ›Invoke, чтобы попытаться прочитать тот же аргумент . Посмотрите, работает ли он. - person Uri; 11.01.2012
comment
4) Все еще не работает ??? выполнить некоторую отладку на уровне сборки. Скачайте и установите WinDBG. Отладка iexplore.exe. Получите символы iexplore.exe с веб-сайта Microsoft. Поместите точку останова в: MSHTML! CBase :: varInvoke. Убедитесь, что используется тот же объект (точка «this» на ebp + 8). - person Uri; 11.01.2012
comment
Спасибо, юри. Я попытался перечислить свойства окон в самом моем тестовом js. Хотя я не уверен, как получить имя [object]. Также моя среда разработки - IE7, поэтому я не могу использовать инструменты разработчика. Я не уверен насчет 3) не могли бы вы объяснить это. Я пробовал 2), но это не работает. Далее я попробую вариант 4). - person Favonius; 11.01.2012
comment
Еще одно обновление: я добавил следующий код if(FAILED(hr)){::MessageBox(NULL, L"Failed", L"Message", MB_OK);} в качестве последнего оператора. Кажется, что dispxWnd->Invoke(...) не работает в IE7. - person Favonius; 11.01.2012
comment
Но это не удается только с DISPATCH_PROPERTYPUTREF, а не с DISPATCH_PROPERTYPUT. Это немного странно, я не понимаю, почему это происходит. - person Favonius; 11.01.2012
comment
При дальнейшем анализе я обнаружил, что с DISPATCH_PROPERTYPUTREF значение hr равно DISP_E_MEMBERNOTFOUND, что согласно MSDN: The requested member does not exist, or the call to InvokeEx tried to set the value of a read-only property.Любые предложения. Спасибо. - person Favonius; 11.01.2012
comment
Определенно, вы всегда должны проверять весь код возврата (код выше упрощен). Еще несколько предложений: 5) Давайте посмотрим, сможете ли вы добавить простое свойство, скажем, целое число. Для целого числа используйте DISPATCH_PROPERTYPUT, и вариант varMyBho будет LONG в конструкторе. Посмотрим, работает ли это. 6) Для объекта автоматизации (IDispatch) это должно быть DIPATCH_PROPERTYPUTREF. Итак, этот призыв терпит неудачу. Если можно пошагово выполнить сборку Invoke mshtml.dll. Установите точку останова на AddRef и QueryInterface (Invoke должен вызывать эти функции) и попытайтесь понять, что происходит. - person Uri; 11.01.2012
comment
Спасибо Ури за помощь. Я все еще борюсь с проблемой. В качестве последнего шага я попробую использовать подход WinDBG. В любом случае еще раз спасибо. +1 ваш ответ. - person Favonius; 12.01.2012
comment
Мне не удалось расширить свой bho, как предложили вы и игорь, но реализация activex работает. В свободное время я постараюсь изучить подход WinDBG. Еще раз спасибо. - person Favonius; 12.01.2012
comment
Что ж, проблема с подходом ActiveX в том, что вы получите другой экземпляр вашего объекта. Это будет один объект, созданный как BHO и доступный в течение всего срока службы браузера (то есть вкладка IE), и будет еще один экземпляр, созданный страницей ActiveX. Если у вас нет гражданства, это не должно быть проблемой, но если у вас есть состояние в BHO, это проблема. Если вы не против поделиться своим кодом, а ваш код достаточно мал, я бы не прочь взглянуть на него. - person Uri; 12.01.2012
comment
Сфера моего применения очень ограничена. Он вставляет библиотеки jquery и jqueryui на любую заданную страницу. Которая затем используется для улучшения внешнего вида существующего приложения. Взаимодействие с js и bho требуется для сохранения текста html в фиксированном месте и всегда в виде текста. Проблема, которую вы указали, очень верна, а также есть угол безопасности, связанный с ActiveX. вот почему я хотел использовать бхо. Я создал простое приложение Bho, чтобы проверить это. Его можно найти на странице code.google.com/p/simple-atl- bho / downloads / list. Спасибо. - person Favonius; 12.01.2012
comment
Привет, @Favonius, не могли бы вы предоставить тестовую среду, которая описывает использование подхода activex? Я нашел несколько статей (вы, наверное, видели blogs.msdn.com/b/nicd/archive/2007/04/18/), но некоторые убедительные примеры были бы для нас немного приятнее С # ребята, которые редко покидают управляемый код :) - person Mike; 24.01.2012
comment
@uri: Спасибо. Наконец, он заработал, то есть вызов BHO из JS и без ActiveX. По крайней мере, работает для IE8 и IE7. Хотя не уверен насчет IE9 и выше. Это была просто глупая ошибка с моей стороны !! Забыл выставить метод в idl. Хотя DISPATCH_PROPERTYPUTREF у меня не работает, вместо этого я попробовал DISPATCH_PROPERTYPUT, и это сработало. Спасибо за ваше время. - person Favonius; 31.01.2012