Javascript和BHO的相互调用简介

v\:* {behavior:url(#default#VML);}
o\:* {behavior:url(#default#VML);}
w\:* {behavior:url(#default#VML);}
.shape {behavior:url(#default#VML);}/* Style Definitions */
table.MsoNormalTable
{mso-style-name:普通表格;
mso-tstyle-rowband-size:0;
mso-tstyle-colband-size:0;
mso-style-noshow:yes;
mso-style-priority:99;
mso-style-parent:"";
mso-padding-alt:0cm 5.4pt 0cm 5.4pt;
mso-para-margin:0cm;
mso-para-margin-bottom:.0001pt;
mso-pagination:widow-orphan;
font-size:10.0pt;
font-family:"Times New Roman","serif";}

作者:magictong

Javascript调用BHO里面的函数

方案一、脚本扩展(window.external)

Window.external本身是浏览器提供的用来调用浏览器的外部方法的,譬如IE里面就提供了很多外部方法(参考[1])。如果我们想脚本能调用插件的函数,可以通过扩展这个接口的方法来实现(也就是脚本扩展)。

BHO实现要点

首先实现IDocHostUIHandler接口([3]),这个接口本身是IE暴露的一个界面替代接口(可以修改部分界面表现),通过ICustomDoc接口的SetUIHandler方法把IDocHostUIHandler设置到相应的页面上面,从而达到改变页面部分界面效果的作用,不过我们这里不用来改变界面,因此IDocHostUIHandler接口的大部分方法都可以简单实现,重点实现GetExternal方法即可。

GetExternal方法的实现也并不是很复杂,它只需要返回一个IDispatch接口即可,熟悉IE框架下面脚本和插件交互流程的童鞋应该很清楚返回这个接口的意义所在。因此这个地方我们需要实现一个继承自IDispatch接口的COM类,这里也是较为麻烦的一个地方。

实现说明

我们可以使用BHO类来实现IDocHostUIHandler接口即可,注意重点实现GetExternal方法,其实也很简单(注意:IDocHostUIHandler的大部分方法都可以返回一个S_OK即可)。

GetExternal的实现:

HRESULT STDMETHODCALLTYPECAdPromotionImp::GetExternal(IDispatch __RPC_FAR*__RPC_FAR *ppDispatch)

{

if(ppDispatch)

{

*ppDispatch = (IDispatch*)(new (std::nothrow) CJSObject());

}

return S_OK;

}

下面重点看一下这个CJSObject类的实现,它需要实现IDispatch接口,意味着你得把IUnknown接口也一并实现。IUnknown接口的实现就不详述了,看看IDispatch接口的四个方法的实现,GetTypeInfoCount和GetTypeInfo简单返回S_OK即可。另外两个方法的实现如下:

HRESULT STDMETHODCALLTYPECJSObject::GetIDsOfNames(

REFIID riid,

LPOLESTR __RPC_FAR*rgszNames,

UINT cNames,

LCID lcid,

DISPID __RPC_FAR*rgDispId)

{

if(!rgszNames || 0== cNames || NULL== rgDispId)

{

return S_OK;

}

*rgDispId = DISPID_UNKNOWN;

CComBSTR bstrRgszName(*rgszNames);

if (bstrRgszName== JS_CALL_FUN)

{

*rgDispId = DISP_ID_TESETSCRIPT;

return S_OK;

}

// 对于参数不正确,依然return S_OK 防止浏览器报脚本错误

return S_OK;

}

HRESULT STDMETHODCALLTYPECJSObject::Invoke(

DISPID dispIdMember,

REFIID riid,

LCID lcid,

WORD wFlags,

DISPPARAMS __RPC_FAR*pDispParams,

VARIANT __RPC_FAR*pVarResult,

EXCEPINFO __RPC_FAR*pExcepInfo,

UINT __RPC_FAR*puArgErr)

{

if(0 == (wFlags& DISPATCH_METHOD ))

{

// 对于参数错误的情况也return S_OK 防止浏览器报脚本错误

return S_OK;

}

if(dispIdMember ==DISP_ID_TESETSCRIPT)

{

::MessageBoxW(0, L"JS调用BHO函数TestScript()", L"TestScript", 0);

}

return S_OK;

}

要实现更复杂的函数就自己去想象吧……

另外,还得找一个地方去调用ICustomDoc接口的SetUIHandler方法哦!在哪里调用就看你的需求了而且要防止重入(在你感兴趣的网页需要且仅需调用一次)。

HRESULThr = E_FAIL;

CComPtr<IDispatch> spDoc;

CComQIPtr<IHTMLDocument2> spHTML;

m_spWebBrowser->get_Document(&spDoc);

spHTML= spDoc;

if(spHTML)

{

CComQIPtr<ICustomDoc, &IID_ICustomDoc>spCustomDoc;

hr = spHTML->QueryInterface(__uuidof(ICustomDoc), (void**)&spCustomDoc);

if (SUCCEEDED(hr) && spCustomDoc)

{

hr = spCustomDoc->SetUIHandler(this);

}

}

有副作用吗?

实现IDocHostUIHandler时要注意,为了避免副作用(因为你替换了系统原来的IDocHostUIHandler接口),在实现该接口的某些方法时为了避免内存泄漏,你应该把方法的调用总是转发给原来的IDocHostUIHandler接口,方法是在SetUIHandler之前先获取原来的IDocHostUIHandler接口并保存,然后在ShowUI和HideUI等方法里面转调源IDocHostUIHandler接口进行处理。

优缺点

在上面的讨论中零零碎碎有提到过,优点的话譬如实现相对来说比较简单等等,缺点就比较明显了副作用比较大,实现IDocHostUIHandler接口时要防止影响原始的功能(譬如另外一个插件也实现了这个接口),应用场景比较有限,另外安全性需要额外小心,因为函数名是固定的,需要保护好函数的参数等等。

测试

<html>

<head>

<script language=‘javascript‘>

function call_external(){

try

{

window.external.TestScript();

}

catch(err)

{

alert(err.description);

}

}

</script>

</head>

<body onload="call_external();">

<center><div><span>HelloMagictong!!</span>

</div>

</center>

</body>

</html>

执行成功

方案二、导出类(属性注入)

上面的方法副作用很多,那么是否有其它的方法可以使用呢?答案是肯定的。还记得上面的方法里面在实现GetExternal时,返回了一个IDispatch接口给外部的脚本执行环境,对window.external空间进行了扩展的,导出类的方式其实也是类似的,但是扩展的是脚本的window空间,大概你可以这样认为,假如你导出了一个名字为KClass的对象给脚本的window空间,同时你的KClass对象实现了IDispatch接口,支持一个名为DoFunciton的函数,那么JS脚本里面就可以这样调用window.KClass.DoFunciton()。我们来看怎么实现。

在上面的方法中,我们已经把实现了IDispatch接口的类实现好了,就是CJSObject,可以直接拿来用,没有问题,关键看导出类的部分就可以了。

bool CAdPromotionImp::__ExtendJsWindow()

{

// 首先获取document2接口

CComPtr<IDispatch>spDispatch;

HRESULT hr = m_spWebBrowser->get_Document(&spDispatch);

if (!SUCCEEDED(hr) || !spDispatch)

return false;

CComQIPtr<IHTMLDocument2>pHTMLDoc(spDispatch);

if (!pHTMLDoc)

return false;

// 获取htmlwindow窗口

CComPtr<IHTMLWindow2>spWindow;

hr = pHTMLDoc->get_parentWindow(&spWindow);

if (!SUCCEEDED(hr) || !spWindow)

return false;

CComQIPtr<IDispatchEx>pHtmlWindow = spWindow;

if (!pHtmlWindow)

return false;

CComBSTR propName(L"KClass");

DISPID dispid= 0;

pHtmlWindow->GetDispID(propName, fdexNameEnsure,&dispid );

if (!SUCCEEDED(hr))

return false;

CComVariant varAdBho((IDispatch *)(new (std::nothrow) CJSObject()));

DISPPARAMS disParams= {&varAdBho, 0, 1, 0};

hr = pHtmlWindow->InvokeEx( dispid,LOCALE_USER_DEFAULT, DISPATCH_PROPERTYPUTREF, &disParams,NULL, NULL,NULL );

if (!SUCCEEDED(hr))

{

hr = pHtmlWindow->InvokeEx( dispid,LOCALE_USER_DEFAULT, DISPATCH_PROPERTYPUT, &disParams,NULL, NULL,NULL );

}

if (!SUCCEEDED(hr))

{

return false;

}

return true;

}

有副作用吗?

基本没有。

优缺点

其实实现起来比上面的方法不会复杂。而且没有副作用,不会干扰其它插件或者浏览器的固有行为。导出类可以做命名随机和加密,可以降低命名冲突的可能性,同时也可以降低其它网页对插件的恶意攻击行为。另外需要注意的是,为了加强代码的健壮性,防止内存泄漏,建议调试时严格控制下导出类的声明周期。

测试

<html>

<head>

<script language=‘javascript‘>

function call_external()

{

try

{

window.KClass.TestScript2();

}

catch(err)

{

alert(err.description);

}

}

</script>

</head>

<body onload="call_external();">

<center><div><span>HelloMagictong!!</span>

</div>

</center>

</body>

</html>

执行成功

方案三、Axtivex控件

这个东东生来的理由就是为了让脚本来调用它,扩展IE的功能,但是因为不在本文的讨论范围,因此不再讨论。

BHO调用Javascript里面的函数

这个相对来说简单一些,主要就是通过调用IHTMLWindow2接口的execScript来完成对JS脚本里面函数的调用。

看这个方法说明,很直观吧。

直接看代码吧。

bool CAdPromotionImp::__CallJSFunction()

{

CComPtr<IDispatch>spDispatch;

HRESULT hr = m_spWebBrowser->get_Document(&spDispatch);

if (!SUCCEEDED(hr) || !spDispatch)

return false;

CComQIPtr<IHTMLDocument2>pHTMLDoc(spDispatch);

if (!pHTMLDoc)

return false;

CComPtr<IHTMLWindow2>spWindow;

hr = pHTMLDoc->get_parentWindow(&spWindow);

if (!SUCCEEDED(hr) || !spWindow)

return false;

VARIANT out;

hr = spWindow->execScript(CComBSTR("whocallme_Function()"), CComBSTR(L"javascript"), &out);

if (SUCCEEDED(hr))

return true;

return false;

}

调用时机

这个就看你的需求了,另外一个需要注意的时,你得等待JS代码(譬如你调用的是一个JS函数)已经被IE加载完成了,BHO再调用才能成功。

测试

<html>

<head>

<script language=‘javascript‘>

function whocallme_Function()

{

alert("BHO call JS function!");

}

</script>

</head>

<body>

<center><div><span>HelloMagictong!!</span>

</div>

</center>

</body>

</html>

执行成功

参考资料

[1] external object http://msdn.microsoft.com/en-us/library/ms535246%28VS.85%29.aspx

[2] Popup WindowBlocker http://www.codeproject.com/Articles/4003/Popup-Window-Blocker

[3] IDocHostUIHandler interface http://msdn.microsoft.com/en-us/library/aa753260(v=vs.85).aspx

[4] JS调用BHO http://www.cnblogs.com/dlbrant/p/3142887.html

[5] 一点编写 BHO 的思路 http://bbs.csdn.net/topics/370215253

时间: 2024-10-12 22:36:50

Javascript和BHO的相互调用简介的相关文章

JQuery javascript实现父子页面相互调用

javascript实现父子页面相互调用 By:授客 QQ:1033553122 场景1 父页面调用子页面 如上图,在iframe子页面的<script>元素中,定义了taskStatus全局变量,如果希望在其父页面中获取该全局变量的值,则可在父页面的<script>元素中新增js脚本如下: var taskStatus = document.getElementById('iframe-1-11').contentWindow.taskStatus; 注:这里iframe-1-1

开源项目ScriptGate,Delphi与JavaScript相互调用的神器

ScriptGate是一个实现TWebBrowser上的JavaScript和Delphi代码相互调用的库,具体在这里:https://bitbucket.org/freeonterminate/scriptgate 用ScriptGate,我们可以轻松实现JavaScript在Delphi上的使用了,目前支持最新的Delphi tokyo 10.2.3版,注意,属于FMX类型的控件,作者说支持Windows,MacOS,Android及iOS. 我该怎么办? 例如,您可以从Delphi调用以下

Hybrid App开发模式中, IOS/Android 和 JavaScript相互调用方式

IOS:Objective-C 和 JavaScript 的相互调用 iOS7以前,iOS SDK 并没有原生提供 js 调用 native 代码的 API.但是 UIWebView 的一个 delegate 方法使我们可以做到让 js 需要调用时,通知 native.在 native 执行完相应调用后,可以用stringByEvaluatingJavaScriptFromString 方法,将执行结果返回给 js.这样,就实现了 js 与 native 代码的相互调用.具体让 js 通知 na

IOS Object和javaScript相互调用

在IOS开发中有时会用到Object和javaScript相互调用,具体步骤如下: 1. Object中执行javascript代码,这个比较简单,苹果提供了很好的方法 - (NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script 2. javascript执行过程中返回给Object的数据或者调用Object方法,这个时候就需要用到 UIWebView的地址重定向功能,主要代码如下: (1)创建UIWebView

[转]C# winform与Javascript的相互调用

C# winform与Javascript的相互调用 <html> <head> <meta http-equiv="Content-Language" content="zh-cn"> <script language="javascript" type="text/javascript"> <!-- 提供给C#程序调用的方法 --> function messag

Android中通过WebView控件实现与JavaScript方法相互调用的地图应用

在Android中通过WebView控件,可以实现要加载的页面与Android方法相互调用,我们要实现WebView中的addJavascriptInterface方法,这样html才能调用android方法,在这里我个人觉得有点和DWR相似. 为了让大家容易理解,我写了一个简单的Demo,具体步骤如下: 第一步:新建一个Android工程,命名为WebViewDemo(这里我在assets里定义了一个html页面). 第二步:修改main.xml布局文件,增加了一个WebView控件还有But

C#代码与JAVASCRIPT函数的相互调用

问:1.如何在JavaScript访问C#函数?2.如何在JavaScript访问C#变量?3.如何在C#中访问JavaScript的已有变量?4.如何在C#中访问JavaScript函数? 问题1答案如下:javaScript函数中执行C#代码中的函数:方法一:1.首先建立一个按钮,在后台将调用或处理的内容写入button_click中;        2.在前台写一个js函数,内容为document.getElementById("btn1").click();        3.

Android:WebView与Javascript交互(相互调用参数、传值)

Android中可以使用WebView加载网页,同时Android端的java代码可以与网页上的javascript代码之间相互调用. 效果图: (一)Android部分: 布局代码: <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_widt

Android高手进阶教程(二十)之---Android与JavaScript方法相互调用!

在Android中通过WebView控件,可以实现要加载的页面与Android方法相互调用,我们要实现WebView中的addJavascriptInterface方法,这样html才能调用android方法,在这里我个人觉得有点和DWR相似. 为了让大家容易理解,我写了一个简单的Demo,具体步骤如下: 第一步:新建一个Android工程,命名为WebViewDemo(这里我在assets里定义了一个html页面). 第二步:修改main.xml布局文件,增加了一个WebView控件还有But