很早之前我写过一篇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. Linux基础(3)- 正文处理命令及tar命令、vi编辑器、硬盘分区、格式化及文件系统的管理和软连接、硬连接

    一.正文处理命令及tar命令 1)  将用户信息数据库文件和组信息数据库文件纵向合并为一个文件1.txt(覆盖) 2)  将用户信息数据库文件和用户密码数据库文件纵向合并为一个文件2.txt(追加) ...

  2. scp windows 和 linux 远程复制 (双向)

    一下命令在cmd中 从w -> l : scp D:\a.txt root@192.168.2.113:/home/a 从l -> w: scp root@192.168.2.113:/h ...

  3. remote connect openshift mysql

    再虚拟机内 rhc port-forward <app-name> 此时,可以在本机 访问 127.0.0.1:8080  登陆 网页, 3306连接sql https://unix.st ...

  4. linux c 网络编程:用域名获取IP地址或者用IP获取域名 网络地址转换成整型 主机字符顺序与网络字节顺序的转换

    用域名获取IP地址或者用IP获取域名 #include<stdio.h> #include<sys/socket.h> #include<netdb.h> int ...

  5. angular 指令封装弹出框效果

    就直接用bs的警告框啦~,Duang~ 功能 可以设置message和type,type就是bs内置的几种颜色 默认提示3秒框自动关闭,或者点击x号关闭 代码 模板 <div class=&qu ...

  6. FFmpeg编码详细流程

    FFmpeg在编码一个视频的时候的函数调用流程.为了保证结构清晰,其中仅列出了最关键的函数,剔除了其它不是特别重要的函数. 函数背景色 函数在图中以方框的形式表现出来.不同的背景色标志了该函数不同的作 ...

  7. Andorid——ubuntu下的 NDK / JNI

    之前一直有接触源代码里面的JNI体系,知道个大概,仅仅管调进了哪个C/C++的接口,如今记录学习下. 撰写不易,转载请注明出处:http://blog.csdn.net/jscese/article/ ...

  8. Memcached中的存取命令详解

    本文和大家分享的主要是Memcached中常用的一些存取命令相关用法,一起来看看吧,希望对大家学习Memcached有所帮互助. 存储命令 set:不管key存在与否,强制进行set操作: add:必 ...

  9. 配置tomcat,访问端口改为80

    首先:找到tomcat的的config文件夹下的server.xml文件: 编辑server.xml 保存server.xml文件,重启tomcat服务器,即可. 亲测好使.

  10. 虚拟机linux安装mysql

    安装mysql时需要的全套安装包 mysql-5.1.73-3.el6_5.i686.rpm mysql-libs-5.1.73-3.el6_5.i686.rpm mysql-server-5.1.7 ...