资讯详情

资讯详情

IE BHO插件开发实战:COM+ATL实现浏览器深度集成

简介本资源是面向C开发者与Windows平台浏览器扩展工程师的IE插件开发实战代码包聚焦COM组件模型下的ATL框架BHOBrowser Helper Object实现解决IE环境中的网页行为监听、内容过滤等定制化功能开发需求。压缩包共17个文件涵盖3个C源文件如dlldata.c用于COM服务器注册、3个CPP实现文件如FiltrateIE.cpp承载核心过滤逻辑、4个头文件含SafeIE.h与SafeIE.idl定义接口契约、2个DEF导出文件及VS6工程配置文件.dsw/.dsp整体仅15KB轻量紧凑且结构完整。已有217人学习下载适合具备基础COM和ATL知识的中高级开发者深入理解BHO生命周期管理、IWebBrowser2交互机制及DLL安全加载流程。读者可直接编译调试该工程掌握从接口定义、ATL封装到IE注册部署的全流程实践尤其适用于遗留系统维护或特定政务/金融IE兼容场景下的插件二次开发。1. 为什么今天还要写 IE 浏览器插件BHO 不是“古董”而是遗留系统不可替代的胶水层你点开一个老式 ERP 系统页面顶部弹出“请使用 Internet Explorer 打开”F12 按下却只看到空白的开发者工具——这不是 bug是真实产线、金融柜台、医疗设备管理后台每天都在发生的现场。IE 浏览器插件编程尤其是基于 COM ATL 实现的 BHOBrowser Helper Object从未真正退出舞台它仍是 Windows 原生桌面应用与 Web 页面深度集成的唯一低侵入路径能劫持导航、注入脚本、监听 DOM 变化、调用本地 DLL、甚至绕过同源策略读取跨域 iframe 内容。标题中的COM ATL BHO.zip不是一个怀旧压缩包而是一套可编译、可注册、可调试的最小可行骨架——它不依赖 .NET Framework不绑定特定 IE 版本IE8–IE11 兼容也不需要管理员权限即可完成注册HKEY_CURRENT_USER。适合两类人一是维护十年以上政企内网系统的前端/客户端工程师二是需要在 IE 环境中桥接 PLC 控制指令、串口通信COM 口、本地硬件 SDK 的工业自动化开发者。别被“IE 已停更”误导IE 模式在 Edge 中仍默认启用且大量 ActiveX 控件、VBScript 脚本、C 插件至今无法被现代浏览器替代。2. COM ATL 构建 BHO 的底层逻辑为什么不用 C# 或 WinRT2.1 BHO 的本质是进程内 COM 服务ATL 是最轻量的 COM 宿主框架BHO 必须以 DLL 形式加载进iexplore.exe进程空间且需实现IObjectWithSite和IDispatch可选接口。关键约束有三必须是 in-process serverDLLEXE 无法被 IE 加载必须支持 Apartment 模型IE 使用单线程单元STABHO 必须声明ThreadingModelApartment注册表路径固定CLSID 必须写入HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Browser Helper Objects\{xxx}或当前用户路径。C# 编写的 BHO 需依赖 .NET Runtime 注册为 COM 可见类但 IE 进程可能未加载 CLR或版本冲突导致Class not registeredWinRT 组件则根本无法注入传统 IE 进程。ATLActive Template Library直接生成原生 C COM 对象无运行时依赖二进制体积小Release 下通常 100KB且 ATL Wizard 自动生成的CComObjectRootEx、CComCoClass等模板已封装好引用计数、线程模型、类型库注册等 boilerplate 代码。这是工业场景下“确定性”的刚需——你不能让一台运行 Windows Server 2008 R2 的工控机去装 .NET 4.8。2.2 用 Visual Studio 2019 创建 ATL BHO 工程的 5 个关键步骤提示必须使用 VS2019 或更早版本VS2022 默认禁用 ATL 支持需手动勾选“C ATL 支持”工作负载新建项目 → ATL 项目名称设为MyBHO取消勾选“安全开发向导”目标平台选x86IE32 位进程不兼容 x64 DLL添加 ATL 简单对象右键项目 → “添加” → “类” → “ATL 简单对象”名称填MyBho在“选项”页勾选✅ Support ISupportErrorInfo错误信息透出✅ Support IObjectWithSiteBHO 必需接口✅ Support connection points如需响应网页事件❌ Don’t support aggregationBHO 不支持聚合修改MyBho.h接口继承链确保类继承自IObjectWithSite并实现其方法// MyBho.h class ATL_NO_VTABLE CMyBho : public CComObjectRootExCComSingleThreadModel, public CComCoClassCMyBho, CLSID_MyBho, public IObjectWithSite { public: CMyBho() : m_spWebBrowser(NULL) {} DECLARE_REGISTRY_RESOURCEID(IDR_MYBHO) BEGIN_COM_MAP(CMyBho) COM_INTERFACE_ENTRY(IObjectWithSite) COM_INTERFACE_ENTRY(IDispatch) // 可选用于 JS 调用 END_COM_MAP() // IObjectWithSite 方法实现见 2.3 STDMETHOD(SetSite)(IUnknown* pUnkSite); STDMETHOD(GetSite)(REFIID riid, void** ppvSite); private: CComPtrIWebBrowser2 m_spWebBrowser; // 存储浏览器接口指针 };在MyBho.cpp中实现SetSite这是 BHO 生命周期起点IE 在创建窗口时调用此方法传入IWebBrowser2STDMETHODIMP CMyBho::SetSite(IUnknown* pUnkSite) { if (pUnkSite ! NULL) { // 获取 IWebBrowser2 接口用于后续操作 HRESULT hr pUnkSite-QueryInterface(__uuidof(IWebBrowser2), (void**)m_spWebBrowser); if (FAILED(hr)) return hr; // 可在此处注册事件如 DocumentComplete CComPtrIDispatch spDisp; hr m_spWebBrowser-get_Document(spDisp); if (SUCCEEDED(hr) spDisp) { // 获取 HTMLDocument 接口注入脚本或监听 DOM } } else { // IE 卸载 BHO 时调用清理资源 m_spWebBrowser.Release(); } return S_OK; }配置项目属性C/C → General → “附加包含目录” 添加$(VCInstallDir)atlmfc\includeLinker → Advanced → “导入库” 设为$(IntDir)MyBHO.libBuild Events → Post-Build Event → 添加注册命令见 2.4。2.3 注册表项与 CLSID 的手工验证方法BHO 的核心是注册表项必须精确匹配。假设你的 CLSID 为{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}由MyBho.rgs文件生成则注册表路径为HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Browser Helper Objects\{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}该键下必须存在一个(默认)字符串值内容为空或任意描述。若要仅对当前用户生效避免管理员权限改用HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Browser Helper Objects\{...}验证是否注册成功打开regedit定位到上述路径确认键存在且无拼写错误运行ie4uinit.exe -ClearIconCache清除图标缓存启动 IE访问任意网页后在任务管理器中查看iexplore.exe进程的“详细信息”页签右键 → “转到服务” —— 若 BHO 加载成功会显示对应 DLL 名称如MyBHO.dll更可靠的方法用 Process MonitorSysinternals 工具过滤iexplore.exe进程的RegOpenKey事件搜索你的 CLSID 字符串确认 IE 确实尝试读取该注册表项。参数名必填说明常见错误ThreadingModel是必须为Apartment写成Both或Free导致 IE 崩溃(默认)值是必须存在可为空字符串键存在但无默认值 → BHO 不加载CLSID 格式是严格按{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}大小写、花括号、连字符缺一不可DLL 路径否注册表不存路径IE 通过LoadLibrary按 PATH 查找DLL 未放至System32或PATH目录3. 让 BHO 真正干活注入脚本、拦截导航、读取页面 DOM 的三板斧3.1 在SetSite中获取IHTMLDocument2并注入全局 JS 函数BHO 最常用能力是向页面注入 JavaScript从而绕过同源限制调用本地 API。关键在于IWebBrowser2的get_Document返回的是IDispatch*需 QueryInterface 到IHTMLDocument2STDMETHODIMP CMyBho::SetSite(IUnknown* pUnkSite) { if (pUnkSite ! NULL) { HRESULT hr pUnkSite-QueryInterface(__uuidof(IWebBrowser2), (void**)m_spWebBrowser); if (FAILED(hr)) return hr; // 获取文档对象 CComPtrIDispatch spDisp; hr m_spWebBrowser-get_Document(spDisp); if (SUCCEEDED(hr) spDisp) { CComQIPtrIHTMLDocument2 spDoc(spDisp); if (spDoc) { // 创建 script 元素并注入 CComPtrIHTMLElement spHead; spDoc-get_documentElement(spHead); if (spHead) { CComPtrIHTMLElement spScript; spDoc-createElement(_bstr_t(script), spScript); if (spScript) { CComVariant varText(Lwindow.myBhoApi { alert: function(msg){ alert(From BHO: msg); } };); spScript-put_innerHTML(varText); CComPtrIHTMLElementCollection spHeadChildren; spHead-get_children(spHeadChildren); spHeadChildren-item(CComVariant(0), CComVariant(0), spScript); spHead-appendChild(spScript, spScript); } } } } } return S_OK; }注意put_innerHTML传入的是CComVariant包裹的BSTR不能直接传LPCWSTRappendChild第二个参数是输出参数接收插入后的节点指针此处可忽略。3.2 拦截BeforeNavigate2事件阻止非法 URL 或重写跳转逻辑IE 提供DWebBrowserEvents2事件接口BHO 可通过Connection Point订阅。在MyBho.h中添加连接点支持// MyBho.h 增加 class ATL_NO_VTABLE CMyBho : ... public IDispEventImpl1, CMyBho, DIID_DWebBrowserEvents2, LIBID_SHDocVw, 1, 1 { public: BEGIN_SINK_MAP(CMyBho) SINK_ENTRY_EX(1, DIID_DWebBrowserEvents2, DISPID_BEFORENAVIGATE2, OnBeforeNavigate2) END_SINK_MAP() void __stdcall OnBeforeNavigate2(IDispatch* pDisp, VARIANT* URL, VARIANT* Flags, VARIANT* TargetFrameName, VARIANT* PostData, VARIANT* Headers, VARIANT* Cancel); };在MyBho.cpp中实现OnBeforeNavigate2void __stdcall CMyBho::OnBeforeNavigate2(IDispatch* pDisp, VARIANT* URL, VARIANT* Flags, VARIANT* TargetFrameName, VARIANT* PostData, VARIANT* Headers, VARIANT* Cancel) { if (URL-vt VT_BSTR URL-bstrVal ! NULL) { CString strUrl(URL-bstrVal); // 示例阻止访问百度 if (strUrl.Find(Lbaidu.com) ! -1) { *Cancel VARIANT_TRUE; // 取消导航 // 可在此处弹窗或写日志 MessageBox(NULL, _T(BHO blocked baidu.com), _T(Navigation Blocked), MB_OK); } } }提示Cancel是VARIANT*类型必须赋值VARIANT_TRUE而非true或1否则无效VARIANT_TRUE定义为0x00000001但直接赋值易出错应使用宏。3.3 解析页面表单并提取敏感字段用IHTMLFormElement和IHTMLInputTextElement当页面含form时BHO 可遍历所有表单元素读取用户名、密码等字段注意仅限 HTTPS 页面HTTP 下 IE 可能拒绝访问 DOMvoid CMyBho::ExtractLoginForm() { CComPtrIDispatch spDisp; m_spWebBrowser-get_Document(spDisp); if (!spDisp) return; CComQIPtrIHTMLDocument2 spDoc(spDisp); if (!spDoc) return; CComPtrIHTMLElementCollection spForms; spDoc-get_forms(spForms); if (!spForms) return; long nLength 0; spForms-get_length(nLength); for (long i 0; i nLength; i) { CComVariant varIndex(i); CComPtrIDispatch spFormDisp; spForms-item(varIndex, CComVariant(), spFormDisp); if (!spFormDisp) continue; CComQIPtrIHTMLFormElement spForm(spFormDisp); if (!spForm) continue; CComPtrIHTMLElementCollection spInputs; spForm-get_elements(spInputs); if (!spInputs) continue; long nInputLen 0; spInputs-get_length(nInputLen); for (long j 0; j nInputLen; j) { CComVariant varJ(j); CComPtrIDispatch spInputDisp; spInputs-item(varJ, CComVariant(), spInputDisp); if (!spInputDisp) continue; CComQIPtrIHTMLInputTextElement spInput(spInputDisp); if (spInput) { CComBSTR bstrType; spInput-get_type(bstrType); if (wcscmp(bstrType, Lpassword) 0) { CComBSTR bstrValue; spInput-get_value(bstrValue); // bstrValue 就是密码明文可加密后发往本地服务 OutputDebugString(LPassword captured: ); OutputDebugString(bstrValue); } } } } }警告此操作涉及用户隐私仅限授权内网审计场景生产环境必须加密传输且符合《个人信息保护法》要求。4. 调试与排错为什么 BHO 不加载注册表、权限、架构全排查4.1 BHO 加载失败的 4 类高频原因及逐级验证法BHO 不工作时切忌盲目重装 IE 或重启电脑。按以下顺序逐项验证DLL 是否被正确加载到进程用 Process Explorer 打开找到iexplore.exe进程 → 右键 → “Properties” → “Image” 页签 → 查看“Loaded Modules”列表中是否存在你的 DLL 名称若不存在说明注册表未生效或 DLL 路径错误注册表 CLSID 是否可读运行reg query HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Browser Helper Objects\{YOUR-CLSID} /s若返回ERROR: The system was unable to find the specified registry key or value说明注册失败ATL 项目是否启用了“在注册表中注册”右键项目 → “属性” → “配置属性” → “常规” → “项目默认值” → 确认“在注册表中注册”为YesPost-Build Event 中的注册命令应为$(TargetPath) /RegServer if errorlevel 1 echo Registration failed!手动注册以管理员身份运行cmd执行MyBHO.dll /RegServerIE 安全设置是否禁用了 BHOIE → 工具 → Internet 选项 → “安全” 页签 → 选择“受信任的站点” → “自定义级别” → 找到“下载未签名的 ActiveX 控件”和“初始化并执行 ActiveX 控件” → 设为“启用”同时确认“受信任的站点”中已添加你的目标网站域名。4.2 用 ATLTRACE 输出调试日志到 Output 窗口ATL 内置ATLTRACE宏无需额外日志库。在关键函数开头添加ATLTRACE(_T(CMyBho::SetSite called, pUnkSite0x%p\n), pUnkSite);然后在 Visual Studio 中调试 → Windows → 输出 → 选择“调试”运行 IE 并触发 BHO 加载Output 窗口将显示日志若无输出检查项目属性 → “配置属性” → “C/C” → “预处理器” → “预处理器定义” 是否包含ATLTRACE默认开启。4.3 常见 COM 错误码速查表错误码十六进制含义解决方案0x80040154Class not registeredDLL /RegServer未执行或 CLSID 注册路径错误0x80070005Access denied当前用户无注册表写入权限改用HKEY_CURRENT_USER路径0x80004002No interfaceBEGIN_COM_MAP中未声明IObjectWithSite或QueryInterface返回E_NOINTERFACE0x8007007EModule not foundDLL 依赖的 VC 运行库缺失用Dependency Walker检查MSVCP140.dll等5. 生产级加固签名、静默注册、多 IE 实例兼容与 COM 口直通技巧5.1 用 Authenticode 签名 DLL 避免 IE 的 SmartScreen 拦截未签名的 BHO 在 Windows 10/11 上会被 SmartScreen 标记为“未知发布者”用户点击“更多选项”才能运行。解决方案申请 EV Code Signing Certificate推荐 DigiCert 或 Sectigo用signtool.exe签名signtool sign /f mycert.pfx /p password /t http://timestamp.digicert.com MyBHO.dll签名后右键 DLL → “属性” → “数字签名” 页签可见有效签名。5.2 静默注册用regsvr32 /s 注册表导入实现免 UI 部署企业批量部署时需避免弹窗。制作install.batecho off regsvr32 /s MyBHO.dll reg import bhoreg.reg echo BHO installed successfully. pause其中bhoreg.reg内容为Windows Registry Editor Version 5.00 [HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Browser Helper Objects\{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}] 注意reg import会覆盖整个键因此.reg文件必须只含目标键不可包含其他无关项。5.3 多 IE 实例兼容每个iexplore.exe进程独立加载 BHOIE 的“新窗口”实际是新进程iexplore.exe -nohome每个进程都会独立加载 BHO。这意味着你在第一个 IE 窗口注入的 JS 函数不会自动出现在第二个窗口m_spWebBrowser成员变量是进程局部的不同 IE 进程间不共享若需跨窗口通信必须借助命名管道CreateNamedPipe或共享内存CreateFileMapping。5.4 BHO 直通 COM 口调用CreateFile打开\\\\.\\COM3并发送 AT 指令工业场景中BHO 常需控制串口设备如 PLC、传感器。在MyBho.cpp中添加HANDLE hCom CreateFile(L\\\\.\\COM3, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); if (hCom INVALID_HANDLE_VALUE) { DWORD err GetLastError(); // 检查 5拒绝访问或 2找不到设备 ATLTRACE(_T(Failed to open COM3, error%lu\n), err); return; } DCB dcb {0}; dcb.DCBlength sizeof(DCB); GetCommState(hCom, dcb); dcb.BaudRate CBR_9600; dcb.ByteSize 8; dcb.Parity NOPARITY; dcb.StopBits ONESTOPBIT; SetCommState(hCom, dcb); char cmd[] AT\r\n; DWORD written; WriteFile(hCom, cmd, strlen(cmd), written, NULL);提示\\\\.\\COMx是 Windows 物理 COM 口的 UNC 路径必须用双反斜杠CreateFile需GENERIC_READ | GENERIC_WRITE权限串口操作后务必调用CloseHandle(hCom)。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →