很早之前我写过一篇Blog:网页通过External接口与WebBrowser交互,文中的交互其实只介绍了JS调用C++的部分,而C++调用JS由于微软自己的例子太多,那篇文章就没介绍,不过我最近遇到了一个新问题,和C++调用JS有关,所以重新梳理了下这块的逻辑,把之前的代码完善了下。

我遇到的问题:
内嵌IE浏览器控件WebBrowser的内嵌页host.html中使用iframe又嵌套了一个页面iframe.html,iframe.html上有个JS方法,我用C++调用不到,而host.html上的JS方法可以正常调用到。

问题分析:
从JS来说,这是个跨域问题,host.html和iframe.html不在一个域内;
JS解决跨域问题的方案其实也是有的,但这个不是我们本文的重点,本文的重点是怎么通过WebBrowser控件直接来解决这个“跨域调用”的问题。

问题的根本原因在于:
调用网页的JS需要拿到IHTMLDocument2接口,而每个iframe都有自己对应的IHTMLDocument2,所以我们只要能拿到iframe对应的IHTMLDocument2就能解决问题了。

解决方案:直接上代码吧

 /* -------------------------------------------------------------------------
// FileName : calljs_helper.h
// Creator : linyehui
// Date : 2013/11/16 01:18:09
// Brief : 调用WebBrowser控件内嵌页上的JS函数,iframe中的也能调到
//
// $Id: $
// -----------------------------------------------------------------------*/
#ifndef __CALLJS_HELPER_H__
#define __CALLJS_HELPER_H__ // -------------------------------------------------------------------------
namespace calljs_helper
{
bool CallFunction(
CComPtr<IWebBrowser2> spIWebBrowser,
LPCTSTR lpFuncName,
const vector<wstring>& paramArray,
CComVariant * pVarResult = NULL,
bool bEnumFrame = true); } // namespace // -------------------------------------------------------------------------
// $Log: $ #endif /* __CALLJS_HELPER_H__ */
 /* -------------------------------------------------------------------------
// FileName : calljs_helper.cpp
// Creator : linyehui
// Date : 2013/11/16 01:18:14
// Brief : 调用WebBrowser控件内嵌页上的JS函数,iframe中的也能调到
//
// $Id: $
// -----------------------------------------------------------------------*/ #include "stdafx.h"
#include "calljs_helper.h" // ------------------------------------------------------------------------- CComPtr<IWebBrowser2> HtmlWindowToHtmlWebBrowser(CComPtr<IHTMLWindow2> spWindow)
{
ATLASSERT(spWindow != NULL);
CComQIPtr<IServiceProvider> spServiceProvider = spWindow;
if (spServiceProvider == NULL)
{
return CComPtr<IWebBrowser2>();
} CComPtr<IWebBrowser2> spWebBrws;
HRESULT hRes = spServiceProvider->QueryService(IID_IWebBrowserApp, IID_IWebBrowser2, (void**)&spWebBrws);
if (hRes != S_OK)
{
return CComPtr<IWebBrowser2>();
} return spWebBrws;
} // Converts a IHTMLWindow2 object to a IHTMLDocument2. Returns NULL in case of failure.
// It takes into account accessing the DOM across frames loaded from different domains.
CComPtr<IHTMLDocument2> HtmlWindowToHtmlDocument(CComPtr<IHTMLWindow2> spWindow)
{
ATLASSERT(spWindow != NULL);
CComPtr<IHTMLDocument2> spDocument;
HRESULT hRes = spWindow->get_document(&spDocument);
if ((S_OK == hRes) && (spDocument != NULL))
{
// The html document was properly retrieved.
return spDocument;
} // hRes could be E_ACCESSDENIED that means a security restriction that
// prevents scripting across frames that loads documents from different internet domains.
CComPtr<IWebBrowser2> spBrws = HtmlWindowToHtmlWebBrowser(spWindow);
if (spBrws == NULL)
{
return CComPtr<IHTMLDocument2>();
} // Get the document object from the IWebBrowser2 object.
CComPtr<IDispatch> spDisp;
hRes = spBrws->get_Document(&spDisp);
spDocument = spDisp;
return spDocument;
} bool CallFunctionInDocument(
CComPtr<IHTMLDocument2> spDocument2,
LPCTSTR lpFuncName,
const vector<wstring>& paramArray,
CComVariant * pVarResult)
{
if (!spDocument2)
return false; CComPtr<IDispatch> spScript;
if (FAILED(spDocument2->get_Script(&spScript))) { return false; } CComBSTR bstrMember(lpFuncName);
DISPID dispid = NULL;
HRESULT hr = spScript->GetIDsOfNames(IID_NULL, &bstrMember, , LOCALE_SYSTEM_DEFAULT, &dispid);
if (FAILED(hr)) { return false; } const int arraySize = paramArray.size(); DISPPARAMS dispparams;
memset(&dispparams, , sizeof dispparams);
dispparams.cArgs = arraySize;
dispparams.rgvarg = new VARIANT[dispparams.cArgs]; for (int i = ; i < arraySize; i++)
{
CComBSTR bstr = paramArray[arraySize - - i].c_str(); // back reading
bstr.CopyTo(&dispparams.rgvarg[i].bstrVal);
dispparams.rgvarg[i].vt = VT_BSTR;
}
dispparams.cNamedArgs = ; EXCEPINFO excepInfo;
memset(&excepInfo, , sizeof excepInfo);
CComVariant vaResult;
UINT nArgErr = (UINT)-; // initialize to invalid arg hr = spScript->Invoke(dispid, IID_NULL, , DISPATCH_METHOD, &dispparams, &vaResult, &excepInfo, &nArgErr); delete [] dispparams.rgvarg;
if (FAILED(hr)) { return false; } if (pVarResult) { *pVarResult = vaResult; } return true;
} void EnumFrame(
CComPtr<IHTMLDocument2> spIHTMLDocument2,
LPCTSTR lpFuncName,const vector<wstring>& paramArray,
CComVariant* pVarResult)
{
if ( !spIHTMLDocument2 )
return; CComPtr< IHTMLFramesCollection2 > spFramesCollection2;
spIHTMLDocument2->get_frames( &spFramesCollection2 ); long nFrameCount=;
HRESULT hr = spFramesCollection2->get_length( &nFrameCount );
if ( FAILED ( hr ) || == nFrameCount )
return; for(long i = ; i < nFrameCount; i++)
{
CComVariant vDispWin2;
hr = spFramesCollection2->item( &CComVariant(i), &vDispWin2 );
if ( FAILED ( hr ) )
continue; CComQIPtr< IHTMLWindow2 > spWin2 = vDispWin2.pdispVal;
if( !spWin2 )
continue; CComPtr < IHTMLDocument2 > spDoc2;
spDoc2 = HtmlWindowToHtmlDocument(spWin2);
if (!spDoc2)
continue; CallFunctionInDocument(spDoc2, lpFuncName, paramArray, pVarResult);
}
} bool calljs_helper::CallFunction(
CComPtr<IWebBrowser2> spIWebBrowser,
LPCTSTR lpFuncName,
const vector<wstring>& paramArray,
CComVariant * pVarResult,
bool bEnumFrame)
{
if (!spIWebBrowser)
return false; CComPtr<IDispatch> spDispDoc;
HRESULT hr = spIWebBrowser->get_Document(&spDispDoc);
if (FAILED(hr))
return false; CComQIPtr<IHTMLDocument2> spDocument2 = spDispDoc;
if (!spDocument2)
return false; CallFunctionInDocument(spDocument2, lpFuncName, paramArray, pVarResult); if (bEnumFrame)
{
EnumFrame(spDocument2, lpFuncName, paramArray, pVarResult);
} return true;
} // -------------------------------------------------------------------------
// $Log: $

完整的例子代码在:这里下载

另外再附上帮助我解决问题的参考资料:
[http://blog.csdn.net/skyremember/article/details/3422841 IHTMLWindow2的get_document方法有时候会返回E_ACCESSDENIED]
[http://blog.csdn.net/nvidiacuda/article/details/9300869 VC++实现浏览器自动填表]
[http://www.itdelphi.com/delphibbs/doc/2007/3845499.htm webbrowser控件的IHTMLDocument3 or2接口的iframe问题(非安全设置)]
[http://www.cnblogs.com/rainman/archive/2011/02/21/1960044.html window.name实现的跨域数据传输]
[http://www.cnblogs.com/rainman/archive/2011/02/20/1959325.html JavaScript跨域总结与解决办法]

The End.

WebBrowser内嵌页面的跨域调用问题的更多相关文章

  1. AJAX跨域调用相关知识-CORS和JSONP(引)

    AJAX跨域调用相关知识-CORS和JSONP 1.什么是跨域 跨域问题产生的原因,是由于浏览器的安全机制,JS只能访问与所在页面同一个域(相同协议.域名.端口)的内容. 但是我们项目开发过程中,经常 ...

  2. 子页面iframe跨域执行父页面定义的JS方法

    问题需求:父页面与子页面iframe跨域嵌套,子页面要触发父页面所定义的js方法.父子页面的数据传递. 下文中会用到一些文件:父页面: parent.html嵌在父页面的子iframe页面:child ...

  3. AJAX跨域调用ASP.NET MVC或者WebAPI服务的解决方案

    问题描述 当跨域(cross domain)调用ASP.NET MVC或者ASP.NET Web API编写的服务时,会发生无法访问的情况. 重现方式 使用模板创建一个最简单的ASP.NET Web ...

  4. 使用jsonp跨域调用百度js实现搜索框智能提示,并实现鼠标和键盘对弹出框里候选词的操作【附源码】

    项目中常常用到搜索,特别是导航类的网站.自己做关键字搜索不太现实,直接调用百度的是最好的选择.使用jquery.ajax的jsonp方法可以异域调用到百度的js并拿到返回值,当然$.getScript ...

  5. 跨域调用webapi

    web端跨域调用webapi   在做Web开发中,常常会遇到跨域的问题,到目前为止,已经有非常多的跨域解决方案. 通过自己的研究以及在网上看了一些大神的博客,写了一个Demo 首先新建一个webap ...

  6. 关于AJAX跨域调用ASP.NET MVC或者WebAPI服务的问题及解决方案

      作者:陈希章 时间:2014-7-3 问题描述 当跨域(cross domain)调用ASP.NET MVC或者ASP.NET Web API编写的服务时,会发生无法访问的情况. 重现方式 使用模 ...

  7. 跨域调用webapi web端跨域调用webapi

    web端跨域调用webapi   在做Web开发中,常常会遇到跨域的问题,到目前为止,已经有非常多的跨域解决方案. 通过自己的研究以及在网上看了一些大神的博客,写了一个Demo 首先新建一个webap ...

  8. jquery Ajax跨域调用WebServices方法

    由于公司需要开发一个手机页面,想提供给同事直接在手机上可以查询SAP资料.数据需要使用js调用webserver来获取. 因为初次使用Jquery调用Webserver,所以期间并不顺利.测试调用We ...

  9. web端跨域调用webapi

    在做Web开发中,常常会遇到跨域的问题,到目前为止,已经有非常多的跨域解决方案. 通过自己的研究以及在网上看了一些大神的博客,写了一个Demo 首先新建一个webapi的程序,如下图所示: 由于微软已 ...

随机推荐

  1. [转]FTP服务器搭建

    下面先说第一中方法: 1.在win7上先开启ftp服务:这里点击确定后,可能会要等一会儿,完成后有时系统会提示重启 2.打开   计算机-->管理-->   在这里我们可以看见刚刚添加的服 ...

  2. Android录屏命令、Android录Gif、Android录视频

    NoHttp开源地址:https://github.com/yanzhenjie/NoHttp NoHttp具体使用文档已公布,你想知道的全都有,请点我移步! 版权声明:转载请注明本文转自严振杰的博客 ...

  3. python-tornado操作

    Tornado 是 FriendFeed 使用的可扩展的非阻塞式 web 服务器及其相关工具的开源版本.这个 Web 框架看起来有些像web.py 或者 Google 的 webapp,不过为了能有效 ...

  4. kubernetes滚动更新

    系列目录 简介 当kubernetes集群中的某个服务需要升级时,传统的做法是,先将要更新的服务下线,业务停止后再更新版本和配置,然后重新启动并提供服务.如果业务集群规模较大时,这个工作就变成了一个挑 ...

  5. AQS实现公平锁和非公平锁

    https://www.cnblogs.com/chengdabelief/p/7493200.html AQS(AbstractQueuedSynchronizer类)是一个用来构建锁和同步器的框架 ...

  6. JavaScript读书笔记(3)-操作符、语句和函数

    1.  操作符 (1)       一元操作符 前置递增和递减操作符,变量的值都是在语句被求值以前改变的:后置相反 (2)       位操作符 在ECMAScript中,对数值进行位操作时,会发生以 ...

  7. c#4.5新语法--自动属性和隐式类型

    1.自动属性    自动属性是c#中属性定义的两种形式的一种:传统属性定义.自动属性.    1.1 传统属性定义        private int _age;        public int ...

  8. linux环境下启动tomcat7出现时间过长(已经编译完成的项目)问题解决!

    已经编译完成的项目,系统启动过程中,提示: INFO: Starting Servlet Engine: Apache Tomcat/7.0.81 Sep 20, 2017 3:17:32 PM or ...

  9. RecyclerView 可以与CollapsingToolbarLayout一起使用

    Item  布局 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:and ...

  10. iOS开发过程中 xcode文件与Finder中文件保持一致 + 支付宝集成出错

    目录 环境 前言 1.使用 Gem 安装 synx 2.直接在终端 Terminal 中开始使用 3.在使用的时候还可以加参数来实现不同的功能 4.解决项目中出现的一些 error 环境 OS X 1 ...