BHO Browser Helper Objects
1 BHO更像是浏览器的一个插件。
2 BHO依靠于COM,所以他只对IE生效,对其他浏览器无效。
一 创建一个ATL DLL工程(RockBHO),生成的主要文件:
RockBHO.cpp 此源文件用于实现通过 DLL 提供 COM 对象的导出
RockBHO.idl 此源文件可用于定义自定义 COM 接口
RockBHO.rgs 此资源文件包含注册和取消注册 DLL 时编写和删除的注册表项
二 为工程添加类 -> ATL Simple Object
Shortname:MyRock
Apartment: No
Support: IObjectWithSite
生成的主要文件
MyRock.h 此头文件包含 BHO 的类定义
MyRock.cpp 此源文件是项目的主文件并且包含COM对象
三 重写SetSite
Internet Explorer 只调用此方法两次,一次用于建立连接,另一次则是在浏览器退出时。
a MyRock.h
#include <shlguid.h> // IID_IWebBrowser2、DIID_DWebBrowserEvents2等 public: STDMETHOD(SetSite)(IUnknown *pUnkSite); // 重写接口 private: CComPtr<IWebBrowser2> m_spWebBrowser; // 定义私有变量
b MyRock.cpp
STDMETHODIMP CMyRock::SetSite(IUnknown *pUnkSite) { // 初始化期间,浏览器将传递一个对其顶层 IWebBrowser2 接口(我们对其进行缓存处理)的引用。非初始化期间,浏览器将传递 NULL。 if (pUnkSite != NULL) { // 缓存指向 IWebBrowser2 的指针。 pUnkSite->QueryInterface(IID_IWebBrowser2, (void**)&m_spWebBrowser); } else { // 在此释放缓存的指针和其他资源。 m_spWebBrowser.Release(); } // 返回基类实现 return IObjectWithSiteImpl<CMyRock>::SetSite(pUnkSite); }
四 重写DllMain
extern "C" BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved) { // BHO 不需要线程级的跟踪,我们可以在 DLL_PROCESS_ATTACH 通知期间调用 DisableThreadLibraryCalls 以避免新线程通知的额外开销。 if (dwReason == DLL_PROCESS_ATTACH) { DisableThreadLibraryCalls(hInstance); } return _AtlModule.DllMain(dwReason, lpReserved); }
五 注册BHO,在RockBHO.rgs的末尾加上下面代码:
a 在注册表里添加一个双字节的NoExplorer=1的键,不让Windows Explorer加载该BHO,因此该BHO只能在ie中运行
b 实际上下面的格式就是注册表目录结构,HKLM是HKEY_LOCAL_MACHINE的缩写
c 我们要注册的是类的CLSID
HKLM { NoRemove SOFTWARE { NoRemove Microsoft { NoRemove Windows { NoRemove CurrentVersion { NoRemove Explorer { NoRemove 'Browser Helper Objects' { ForceRemove '{4C4B90DD-CAD8-434D-9C0D-24B99A265038}' = s 'MyRock Class' { val 'NoExplorer' = d '1' } } } } } } } }
六 简单的测试
a MyRock.h
#include <exdispid.h> // DISPID_DOCUMENTCOMPLETE class ATL_NO_VTABLE CMyRock : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<CMyRock, &CLSID_MyRock>, public IObjectWithSiteImpl<CMyRock>, public IDispatchImpl<IMyRock, &IID_IMyRock, &LIBID_RockBHOLib, /*wMajor =*/ 1, /*wMinor =*/ 0>, public IDispEventImpl<1, CMyRock, &DIID_DWebBrowserEvents2, &LIBID_SHDocVw, 1, 1> // 用于事件处理 public: void STDMETHODCALLTYPE OnDocumentComplete(IDispatch *pDisp, VARIANT *pvarURL); BEGIN_SINK_MAP(CMyRock) SINK_ENTRY_EX(1, DIID_DWebBrowserEvents2, DISPID_DOCUMENTCOMPLETE, OnDocumentComplete) END_SINK_MAP() // 事件注册(完成载入) private: BOOL m_fAdvised;
b MyRock.cpp
STDMETHODIMP CMyRock::SetSite(IUnknown* pUnkSite) { if (pUnkSite != NULL) { // 缓存指向 IWebBrowser2 的指针。 HRESULT hr = pUnkSite->QueryInterface(IID_IWebBrowser2, (void **)&m_spWebBrowser); if (SUCCEEDED(hr)) { // 注册以从 DWebBrowserEvents2 中汇集事件。 hr = DispEventAdvise(m_spWebBrowser); if (SUCCEEDED(hr)) { m_fAdvised = TRUE; } } } else { // 取消注册事件 if (m_fAdvised) { DispEventUnadvise(m_spWebBrowser); m_fAdvised = FALSE; } // 在此释放缓存的指针和其他资源。 m_spWebBrowser.Release(); } // 调用基类实现。 return IObjectWithSiteImpl<CMyRock>::SetSite(pUnkSite); } void STDMETHODCALLTYPE CMyRock::OnDocumentComplete(IDispatch *pDisp, VARIANT *pvarURL) { // 从站点检索顶级窗口。 HWND hwnd; HRESULT hr = m_spWebBrowser->get_HWND((LONG_PTR*)&hwnd); if (SUCCEEDED(hr)) { // 加载页面时输出消息框。 MessageBox(hwnd, L"大家好!", L"BHO", MB_OK); } }
七 调用IE DOM(删除所有Image)
a MyRock.h
#include <mshtml.h> // DOM 接口 private: void RemoveImages(IHTMLDocument2 *pDocument);
b MyRock.cpp
void STDMETHODCALLTYPE CMyRock::OnDocumentComplete(IDispatch *pDisp, VARIANT *pvarURL) { // 从站点检索顶级窗口。 HWND hwnd; HRESULT hr = m_spWebBrowser->get_HWND((LONG_PTR*)&hwnd); if (SUCCEEDED(hr)) { // 加载页面时输出消息框 // MessageBox(hwnd, L"大家好!", L"BHO", MB_OK); } CComQIPtr<IWebBrowser2> spTempWebBrowser = pDisp; // 此事件是否与顶级浏览器相关联 if (spTempWebBrowser && m_spWebBrowser && m_spWebBrowser.IsEqualObject(spTempWebBrowser)) { // 从浏览器中获取当前文档对象 CComPtr<IDispatch> spDispDoc; hr = m_spWebBrowser->get_Document(&spDispDoc); if (SUCCEEDED(hr)) { // 并查询 HTML 文档 CComQIPtr<IHTMLDocument2> spHTMLDoc = spDispDoc; if (spHTMLDoc != NULL) { // 最后,删除这些图像。 RemoveImages(spHTMLDoc); } } } } void CMyRock::RemoveImages(IHTMLDocument2* pDocument) { CComPtr<IHTMLElementCollection> spImages; // 从 DOM 中获取图像集。 HRESULT hr = pDocument->get_images(&spImages); if (hr == S_OK && spImages != NULL) { // 获取集合中的图像数。 long cImages = 0; hr = spImages->get_length(&cImages); if (hr == S_OK && cImages > 0) { for (int i = 0; i < cImages; i++) { CComVariant svarItemIndex(i); CComVariant svarEmpty; CComPtr<IDispatch> spdispImage; // 按索引从集合中获取图像。 hr = spImages->item(svarItemIndex, svarEmpty, &spdispImage); if (hr == S_OK && spdispImage != NULL) { // 首先,查询通用 HTML 元素接口…… CComQIPtr<IHTMLElement> spElement = spdispImage; if (spElement) { // ……然后请求样式接口。 CComPtr<IHTMLStyle> spStyle; hr = spElement->get_style(&spStyle); // 设置 display="none" 以隐藏图像。 if (hr == S_OK && spStyle != NULL) { static const CComBSTR sbstrNone(L"none"); spStyle->put_display(sbstrNone); } } } } } } }