Вызов метода BHO из Javascript?
Я пытаюсь вызвать свой метод BHO из javascript. Проблема такая же, как указано в следующих сообщениях:
Третья ссылка - это еще одна публикация, в которой говорится об этом, но я не понимал необходимости и кода. Кроме того, общий рабочий образец продолжает сбой на окнах 7, то есть 8 и Windows Vista, т.е. 7.
Если это помогает, мой BHO написан на С++ с использованием ATL.
Что я пробовал:
Я написал очень простой BHO и попробовал подход, упомянутый здесь от Игорь Тандетник. Сгенерировано исключение, но когда я открываю следующий html файл в IE, тогда он говорит объект 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>
Вопрос:
- Просьба уточнить, можно ли разоблачить и вызвать метод BHO из javascript или мне нужно разоблачить его с помощью activex (как ответил jeffdav в [2])? Если да, то как это сделать.
- В принципе, я хочу расширить
window.external
, но способ, показанный в приведенной выше ссылке [2] использует var x = new ActiveXObject("MySampleATL.MyClass");
; Являются ли оба вызывающих соглашения одинаковыми или разными?
Примечание:
- В SO есть связанная запись, которая дает намек на то, что это возможно, вставив этот
[id(1), helpstring("method DoSomething")] HRESULT DoSomething();
в файл BHO IDL. Я не уверен, как это было сделано и не удалось найти вспомогательный ресурс через Google.
- Мне известно это сообщение calling-into-your-bho-from-a-client-script, но он не пробовал, так как он решает проблему с помощью ActiveX.
- Моя причина избегать использования ActiveX в первую очередь из-за ограничений безопасности.
Изменить 1
Кажется, есть способ расширить
window.external
. Проверьте
этот. В частности, раздел под названием
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,
¶ms, 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 post. Issue Resolved.
Ответы
Ответ 1
Метод Игоря Тандетника - правильный подход. Проблема с сообщением заключается в том, что пример кода (по крайней мере, на нескольких страницах, которые я заметил) заключается в том, что он не был полным. У меня было много испытаний и ошибок, пока я не заработал.
Вот хороший кусок моего кода, который делает трюк:
Скажем, у вас есть класс CMyBho, и вы хотите открыть объект автоматизации IMyBho для сценариев Java.
Определение класса:
Вы выходите из стандартных CComObjectRootEx и CComCoClass, чтобы сделать его "co creatable". У вас есть 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,
¶ms, NULL, NULL, NULL );
}
Что касается других вопросов:
-
Очевидно, мой ответ подразумевает, что вы можете сделать объект автоматизации доступным для сценариев из вашего BHO. Также возможно, что ваш объект будет создан с помощью нового объекта ActiveXObject. В этом случае не забудьте указать IE, что ваш объект безопасен для сценариев (обратите внимание: сделайте свой BHO безопасным для сценариев. Убедитесь, что вредоносный веб-сайт не сможет использовать ваш BHO).
-
Я думаю, что window.myBho - лучшее место, чем window.external.myBho. Семантически, "внешний" - это когда mshtml
управление браузером размещается в другом приложении.
Надеюсь, что это помогло.