资讯动态

易语言WebBrowser2支持库二次修改:IE内核兼容与内存泄漏修复实战

发布时间:2026/9/1 6:42:04 来源:尧图企业网站定制
简介WebBrowser2.fne 是易语言环境下的 Web 浏览器支持库基于云外归鸟开源工程调整而来面向需要在易语言程序中嵌入网页浏览、网页交互功能的开发者。解决的核心问题是让易语言应用直接调用 WebBrowser 控件完成 HTML 加载、JavaScript 执行、HTTP 请求等操作比自行封装 IE 内核更省力。ZIP 包内共 29 个文件压缩后约 56KB包含 11 个 .h 头文件、8 个 .cpp 源文件以及 .dsp/.dsw 工程文件、.rc 资源文件、示例 BMP 和开发文档基本覆盖了从源码编译到二次开发的完整骨架。其中自带的 ReadMe 与开发文档可帮助快速定位关键接口。已有 358 人浏览学习适合刚接触支持库开发、或希望改造 WebBrowser2.fne 的易语言用户。通过阅读源码可以理解 HtmlFrame、WebBrowser 等核心类如何组织以及视图、文档与 IE 内核之间如何衔接便于后续增加新功能、修复兼容性问题或调整界面行为。1. 为什么需要WebBrowser2支持库关注易语言浏览器方案的朋友应该都有体会官方自带的超文本浏览框控件在应对现代网页时越来越吃力。老版本IE内核默认渲染模式停留在IE7标准随便打开一个带CSS3动画或Flex布局的页面就是乱版、白屏、控件失效更不用说和页面里的JavaScript做双向交互了想拿到页面里的Cookie、自定义请求头、拦截页面弹窗基本是门都没有。WebBrowser2支持库要解决的核心问题就在这里它是基于IE内核COM接口IWebBrowser2做的一层封装相当于把浏览器内核的底层能力直接暴露给易语言。你可以在程序里动态创建浏览器实例、手动控制导航、注入JS代码、修改HTTP请求头、接管下载事件、调用页面里的JS函数还能让页面里的JS反过来调用易语言子程序。相比官方的超文本浏览框它更像是“拿遥控器操作浏览器内核”而不是“只给你一个看页面的窗口”。这一版WebBrowser2.fne是在云外归鸟开源的WebBrowser2支持库基础上二次修改得到的。云外归鸟在易语言开源圈里算是老牌作者了他早期的几个开源支持库WebBrowser2、WebSocket、Socket代码结构都很清晰接口也完整。我这次修改的出发点很直接原版库在功能上已经很好用但存在几个明显痛点——IE内核默认版本太老、部分接口频繁调用会内存泄漏、缺少某些实用命令比如清空Cookie、设置代理、接管新窗口打开。所以花了几个晚上把源码通读了一遍边用边改最终整理出这个版本。这篇文章把修改思路、踩过的坑、编译过程和常用排查方法都写出来给后续折腾WebBrowser2的朋友做个参考。2. 整体设计思路为什么选云外归鸟的代码以及我的修改方向2.1 选型官方控件也好但需要更多控制力市面上有很多浏览器相关方案易语言里常见的还有精易Web浏览器基于miniblink、水星浏览器基于CEF、WebView2封装。这些方案有一个共同点把整个浏览器内核打包进支持库体积大、依赖VC运行库、内存占用高。如果你的程序只是简单展示网页这些方案完全够用。但如果目标机器的系统比较老比如Windows 7、Windows Server 2008或者软件本身追求体积小、无额外依赖基于系统自带的IE内核是最省事的——因为IE内核在Windows里是默认存在的不需要额外分发几十MB的运行时文件。云外归鸟版本的WebBrowser2支持库恰好满足这个场景。它封装的是系统自带的IE内核COM接口编译出来的成品体积只有几十KB运行时不依赖任何额外的DLL。而且原版代码用C写的接口划分已经很清晰事件回调和命令封装都齐全基于它做二次修改比自己从零实现一套COM封装要高效得多。2.2 修改的核心方向兼容现代网页优先原版WebBrowser2在使用中最大的问题是注册表里没有声明程序对IE内核版本的支持。默认情况下IE内核会按IE7兼容模式渲染这在今天几乎不可用。我的第一个修改方向就是解决这个兼容性问题——在支持库初始化时自动将当前程序的exe名称写入注册表的Feature Control功能控制项强制使用系统最高可用的IE渲染引擎比如Windows 10上就是IE11的WebBrowser控件模式。这样网页的CSS3、HTML5特性都能正常工作了。第二个修改方向是补充实用命令。原版库在创建、导航、事件这些基础功能上没问题但一些日常用得很频繁的功能反而缺失。比如设置User-Agent浏览器标识做适配非常有用清理浏览器缓存和Cookie尤其是做账号切换的时候不清理就要重启程序拦截新窗口打开原版库遇到window.open()或target_blank链接时可能会弹出一个独立的IE窗口很难看我改成了在当前实例内部打开。第三个方向是内存和稳定性。原版库在频繁刷新页面、大量执行JS注入时会出现内存只增不减的情况。经过排查问题出在COM接口引用计数没有正确释放。这个在修改里做了重点修复细节点后面单列一节讲。2.3 修改的工作量评估如果只是改改注册表和加两个命令很快。但实际做下来发现加命令得同步改支持库定义文件.fne对应的静态库定义和函数列表还得改易语言侧的命令说明和参数定义整套流程要走完涉及源码、编译、安装验证三个环节。想动手改的朋友建议预留一两天时间至少要熟悉易语言支持库SDK的编译流程这部分后面给出详细步骤。3. 支持库底层原理与编译前的准备工作3.1 WebBrowser2支持库的本质易语言对COM接口的一层外壳先把这个支持库的内部结构说清楚后面改起来就有方向感。WebBrowser2支持库本质上是一个DLL使用易语言支持库SDK开发。它做了两件事第一把IWebBrowser2相关的COM接口方法封装成易语言能识别的命令比如“浏览”、“后退”、“取网页源码”第二把COM事件回调转换成易语言的事件调用比如“载入完毕事件”、“标题改变事件”。易语言支持库的调用机制比较简单易语言程序启动时加载.fne和对应的静态库文件通常是.fnk或.lib在主程序里调用命令时静态库把参数打包传给DLL里的导出函数执行完以后再返回结果。所以你会看到支持库命令的底层实现都是在C里写DLL导出函数通过支持库SDK提供的数据类型和API和易语言侧完成对接。要二次修改就得拿到源码云外归鸟开源过C工程主要文件包括WebBrowser2.cpp核心实现包括COM调用、事件封装、命令入口WebBrowser2.def导出函数定义决定哪些命令暴露给易语言webBrowser2.fne和webBrowser2_static.lib编译打包出来的成果。3.2 编译环境搭建改源码之前要先保证能编译。我用的搭建方案如下项目选择备注易语言版本5.3以上我用5.71低版本可能有SDK不兼容问题易语言支持库SDK官方下载安装目录e\sdk\包含e_compiler.h、e_sdk.h、lib\e_static.lib等C编译器Visual Studio 2008VC6对部分COM头文件支持不完整VS2008最稳源码云外归鸟开源版确保代码完整含.dsp或.sln工程文件辅助工具Process Explorer排查DLL加载失败、句柄泄漏时用得上打开工程后需要把“包含目录”里加上易语言SDK的路径e\sdk和e\sdk\static库目录里加上e\sdk\lib。然后在工程属性里把运行库改成“多线程DLL/MD”因为易语言主程序本身是多线程环境使用静态运行库容易引起冲突。编译成功后会生成一个WebBrowser2.fne和对应的静态lib。注意fne是动态版易语言独立编译时要用静态lib则是给静态编译用的。两个文件都要拷贝到易语言的lib目录下然后在易语言里选择“工具”→“支持库配置”→“全选”→“确定”新版本才能被调用。3.3 原版代码定位修改点拿到源码后别急着改先找三个关键位置第一WebBrowser2.cpp里类初始化的位置。云外归鸟的代码写了一个CWebBrowser2::Initialize()方法在这个方法里可以挂载注册表设置。第二命令表定义区。易语言支持库的命令表是一个静态数组数组元素包含命令名称、参数个数、命令函数入口等。新增命令要在数组里加一项同时实现对应的导出函数。第三析构函数Release()和COM接口清理的位置。原版的事件接口DWebBrowserEvents2在析构时没有正确调用Release()这是内存泄漏的主要源头。修改时盯着FinalRelease()和Invoke()方法看就行。4. 实操过程三个核心修改点的实现细节4.1 强制启用IE11内核这一步解决现代网页兼容性问题。原理很简单IE内核在加载时会查询注册表HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_BROWSER_EMULATION找到当前exe名对应的值这个值决定了用哪个版本的渲染引擎。支持库初始化时动态写入注册表的代码如下C#include windows.h #include string // 设置当前进程的IE内核版本 void SetIEVersion() { // 读取当前exe名称 char szExe[MAX_PATH]; GetModuleFileNameA(NULL, szExe, MAX_PATH); std::string strExe szExe; // 截取纯文件名 size_t pos strExe.rfind(\\); if (pos ! std::string::npos) strExe strExe.substr(pos 1); // 打开注册表键 HKEY hKey; DWORD dwValue 11001; // 11001: IE11行为模式 // 0x2AF9 11001, 也就是IE11的文档模式编号 RegCreateKeyExA(HKEY_CURRENT_USER, Software\\Microsoft\\Internet Explorer\\Main\\FeatureControl\\FEATURE_BROWSER_EMULATION, 0, NULL, 0, KEY_SET_VALUE, NULL, hKey, NULL); if (hKey ! NULL) { RegSetValueExA(hKey, strExe.c_str(), 0, REG_DWORD, (BYTE*)dwValue, sizeof(dwValue)); RegCloseKey(hKey); } }写入的注册表值有几个档位具体含义如下值含义11001IE11渲染引擎最推荐现代网页基本都能处理10001IE10模式9999IE9模式8888IE8模式强制标准使用建议优先写11001。如果目标机器是Windows 7且没装IE11系统会自动回退到已安装的最高版本不需要你做额外判断。这里要注意一个关键点注册表必须写HKEY_CURRENT_USER不要写HKEY_LOCAL_MACHINE。因为普通用户exe运行时没有管理员权限写HKLM会失败。把这个代码放在Initialize()方法开头执行确保浏览器实例创建前生效。4.2 新增“设置User-Agent”命令翻页看原版代码发现只有“取User-Agent”没有“置User-Agent”。网页适配经常要伪装UA比如微信浏览器UA、手机版UA这个命令必须补上。实现的思路是拿到IWebBrowser2指针后通过QueryInterface获取IHttpEquiv接口或者更直接的办法是使用UrlMkSetSessionOption这个API来设置会话级UA它不需要对COM接口做太多间接操作实现起来最稳。C代码片段#include wininet.h #include UrlMon.h #pragma comment(lib, wininet.lib) void SetUserAgent(const char* ua) { // 设置会话级别的UserAgent UrlMkSetSessionOption(URLMON_OPTION_USERAGENT, ua, (DWORD)strlen(ua), 0); }这个API的作用是全局修改当前程序进程中所有网络请求的UA头。设置在浏览器实例创建之前调用可以在整个程序中保持同一UA如果后续要动态切换UA记得在切换后重新执行导航因为已经发出的请求还是旧的UA。易语言侧的命令导出定义模板如下static void __stdcall e_SetUserAgent(pparam inf) { if (inf-parameterCount 1) return; std::string ua inf-parameters[0].str; SetUserAgent(ua.c_str()); }然后在命令表数组中增加一个命令项填上命令名称“置UserAgent”、参数个数1、函数指针指向e_SetUserAgent重新编译即可。4.3 修复内存泄漏COM接口释放排查内存泄漏时用Process Explorer观察一个不断刷新页面的程序发现每次刷新后内存都会上涨说明COM对象没有释放干净。老代码里常见的问题是在Invoke()事件回调里对事件源对象IDispatch*没有调用Release()或者对IWebBrowser2指针重复AddRef()却只在某个分支里Release()。对于事件回调有一个关键原则DWebBrowserEvents2事件从COM回调过来时接口指针是借用给你的不能在回调里随便Release()。云外归鸟的代码里Invoke()的调用者COM底层会统一释放我们只需要在进入回调时保存需要用到的数据即可。但原来的代码里有些分支会错误地调用Release()导致引用计数失衡后续的浏览器实例一连串崩溃。我的修改方法是把所有事件回调里的Release()全部去掉统一在主循环里维护一个IDispatch引用初始化时AddRef()销毁时统一Release()。这样引用计数唯一且可控泄漏问题迎刃而解。另外还有一个隐藏很深的坑多次调用“浏览”命令时旧页面的事件对象没有及时卸载。修改时在每次导航前先调用DisconnectEvents()断开事件接收再创建新页面。这相当于每次切换页面前先“收拾干净”避免旧页面的事件一直挂在进程里。5. 编译、安装与易语言侧调用测试5.1 编译流程全记录整个编译过程比较简单但需要注意几个容易报错的地方。在VS2008里按F7编译如果提示无法打开包括文件:“e_common.h”说明SDK包含目录没配置好无法解析的外部符号 _e_compiler_error说明没有链接易语言的静态库文件。每次编译报错我都会先检查这两项通常会解决95%的问题。编译成功后输出路径下会生成两个文件WebBrowser2.fne、WebBrowser2_static.lib。把它们全部复制到易语言安装目录的lib\下如果原来有旧版先删除再复制。然后在易语言里打开“工具”→“支持库配置”全选后点“确定”弹窗提示重启IDE才能生效照做即可。5.2 易语言侧的调用测试测试程序很简单三个按钮、一个组合框一个创建浏览器窗口、一个加载百度、一个调JS注入。核心代码段如下.版本 2 .支持库 WebBrowser2 .子程序 _创建按钮_被单击 WebBrowser1.初始化 (取窗口句柄 (), 0, 0, 500, 400) WebBrowser1.浏览 (“https://www.baidu.com”, , , , )这里重点测试三个点网页能否正常加载F12开发者工具能否显示IE11模式事件“载入完毕”能否触发且多次刷新后内存不再一直涨读取网页源码、执行JS、设置UA等命令是否正常。如果新窗口打开还是跑到独立IE窗口检查是否在事件里设置了取消参数。云外归鸟代码里有个参数“取消”如果为真就是拦截新窗口的开关。在“新窗口2”事件里把“取消”设为真再自行在同一个浏览器里导航过去。6. 常见问题与排查技巧实录6.1 编译期问题现象原因解决办法编译提示找不到SDK头文件包含目录未配置VS工程属性里加上易语言SDK路径链接报错无法解析的外部符号没链接e_static.lib“链接器”→“输入”→“附加依赖项”加上编译出来的fne在易语言里加载失败运行库模式不对工程属性改为“多线程DLL/MD”修改命令后易语言侧没新命令忘了同步命令表数组核对源码里命令数组的“命令名称、参数个数”6.2 运行期问题现象原因解决办法网页白屏、版式错乱注册表FEATURE_BROWSER_EMULATION没写成功检查exe名称是否带上了.exe后缀确认写入HKCU新窗口弹到独立IE窗口事件“新窗口2”里没拦截在事件里把“取消”设为真然后用“浏览”跳转页面不断刷新内存上涨导航前事件未断开在每次浏览命令前先调用断开事件统一Release执行JS时对象不存在报错页面尚未完全加载等“载入完毕”事件触发后再执行JS程序异常退出COM未初始化创建浏览器实例前调用CoInitialize(NULL)6.3 使用体验中的几个避坑技巧注册表版本值别写太死。11001在未安装IE11的系统上不会报错但我见过有些老机器在注册表值写入后反而导致网页无法导航。稳妥的做法是先检测系统IE版本如果主版本号低于11就写入9999而不是11001。检测可以用下方API// 获取IE版本 DWORD GetIEVersion() { HKEY hKey NULL; DWORD dwVersion 0; if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, SOFTWARE\\Microsoft\\Internet Explorer, 0, KEY_READ, hKey) ERROR_SUCCESS) { DWORD dwSize sizeof(dwVersion); RegQueryValueExA(hKey, svcVersion, NULL, NULL, (BYTE*)dwVersion, dwSize); RegCloseKey(hKey); } return dwVersion; }多线程场景要小心WebBrowser2的实例隔离。如果你在程序里开了多个浏览器窗口每个窗口必须独立创建WebBrowser1的对象实例不能用一个全局对象控制多个窗口。易语言支持库里虽然内部有全局数据的部分但浏览器的COM引用计数不是线程安全的建议全部放在主线程调度不然会出现奇怪的JS调用失败。调试时把错误显示打开。云外归鸟的库源码里通常有一个宏定义控制是否显示COM错误信息在DEBUG版本里开启这个宏可以看到具体是哪个接口调用失败比自己瞎猜高很多。实际经验里很多问题出在参数类型不匹配而不是COM本身的问题。6.4 对后续使用者的两个建议如果只是日常使用用编译好的fne就够了。但如果想做更深度的浏览器控制比如请求拦截、改响应数据、接管域名解析建议再进一步研究“IDownloadManager”和“IHttpNegotiate”两个接口云外归鸟的库里有部分预留接口可以直接扩展。改动方向清楚编译环境都配好的情况下加一个新接口的时间大概在一两个小时之间。另外针对僵尸页面和卡死问题建议在程序里加一个“超时强制刷新”的机制。因为IE内核自身的稳定性不算好长时间挂机之后页面资源泄露很难彻底避免。做得聪明的做法是在“下载开始”事件里启动一个定时器如果超过10秒没有“下载完成”或“载入完毕”就调用停止()再重新浏览()。这个兜底逻辑实测能大幅降低白屏率。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价