This page has source code for a minimal C++ Browser Helper Object for Internet Explorer. It intercepts keyboard and mouse events, and changes the page's background color.
This code is heavily based on the code at http://www.adp-gmbh.ch/win/com/bho.html. What I've done is turn it into a complete project, one that compiles and builds cleanly, I've added COM registration stuff, and I updated bits of the code to use native interfaces (e.g. IHTMLEvents) instead of querying everything through IDispatch.
#include <windows.h> #include <tchar.h> #include <exdisp.h> #include <exdispid.h> #include <mshtml.h> #include <mshtmdid.h> #include <shlwapi.h> HINSTANCE hInstance; LONG gref=0; const CLSID BhoCLSID = {0xC9C42510,0x9B41,0x42c1,0x9D,0xCD,0x72,0x82,0xA2,0xD0,0x7C,0x61}; #define BhoCLSIDs _T("{C9C42510-9B41-42c1-9DCD-7282A2D07C61}") class BHO : public IObjectWithSite, public IDispatch { long ref; IWebBrowser2* webBrowser; IHTMLDocument* doc; IHTMLDocument2 *doc2; IHTMLWindow2 *win2; public: // IUnknown... HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppv) {if (riid==IID_IUnknown) *ppv=static_cast<BHO*>(this); else if (riid==IID_IObjectWithSite) *ppv=static_cast<IObjectWithSite*>(this); else if (riid==IID_IDispatch) *ppv=static_cast<IDispatch*>(this); else return E_NOINTERFACE; AddRef(); return S_OK;} ULONG STDMETHODCALLTYPE AddRef() {InterlockedIncrement(&gref); return InterlockedIncrement(&ref);} ULONG STDMETHODCALLTYPE Release() {int tmp=InterlockedDecrement(&ref); if (tmp==0) delete this; InterlockedDecrement(&gref); return tmp;} // IDispatch... HRESULT STDMETHODCALLTYPE GetTypeInfoCount(unsigned int FAR* pctinfo) {*pctinfo=1; return NOERROR;} HRESULT STDMETHODCALLTYPE GetTypeInfo(unsigned int iTInfo, LCID lcid, ITypeInfo FAR* FAR* ppTInfo) {return NOERROR;} HRESULT STDMETHODCALLTYPE GetIDsOfNames(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgDispId) {return NOERROR;} HRESULT STDMETHODCALLTYPE Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR* pDispParams, VARIANT FAR* pVarResult, EXCEPINFO FAR* pExcepInfo, unsigned int FAR* puArgErr) { // DISPID_DOCUMENTCOMPLETE: This is the earliest point we can obtain the "document" interface if (dispIdMember==DISPID_DOCUMENTCOMPLETE) { if (!webBrowser) return E_FAIL; IDispatch *idisp; webBrowser->get_Document(&idisp); if (idisp && !doc) idisp->QueryInterface(IID_IHTMLDocument, (void**)&doc); if (idisp && !doc2) idisp->QueryInterface(IID_IHTMLDocument2, (void**)&doc2); if (doc2 && !win2) doc2->get_parentWindow(&win2); IConnectionPointContainer *cpc=0; if (doc) doc->QueryInterface(IID_IConnectionPointContainer, (void**) &cpc); IConnectionPoint* cp=0; if (cpc) cpc->FindConnectionPoint(DIID_HTMLDocumentEvents2, &cp); DWORD cookie; HRESULT hr; if (cp) hr=cp->Advise(static_cast<IDispatch*>(this), &cookie); if (cp) cp->Release(); if (cpc) cpc->Release(); if (idisp) idisp->Release(); if (!doc || !doc2 || !win2 || hr!=S_OK) {release(); return E_FAIL;} return NOERROR; } if (dispIdMember==DISPID_HTMLDOCUMENTEVENTS_ONCLICK) { // This shows how to respond to simple events. MessageBox(0,_T("Try pressing some keys on the keyboard!"),_T("BHO"),MB_OK); return NOERROR; } if (dispIdMember==DISPID_HTMLDOCUMENTEVENTS_ONKEYDOWN) { // This shows how to examine the "event object" of an event IDispatch *param1=0; if (pDispParams->cArgs==1 && (pDispParams->rgvarg)[0].vt==VT_DISPATCH) param1=(pDispParams->rgvarg)[0].pdispVal; IHTMLEventObj *pEvtObj=0; if (param1) param1->QueryInterface(IID_IHTMLEventObj, (void**)&pEvtObj); long keycode; HRESULT hr; if (pEvtObj) hr=pEvtObj->get_keyCode(&keycode); if (pEvtObj) pEvtObj->Release(); if (!pEvtObj || hr!=S_OK) return E_FAIL; // This shows how to manipulate the CSS style of an element int i=keycode-32; if (i<0) i=0; if (i>63) i=63; i*=4; wchar_t buf[100]; wsprintfW(buf,L"rgb(%i,%i,%i)",i,255-i,i/2); IHTMLElement *body=0; doc2->get_body(&body); IHTMLStyle *style=0; if (body) body->get_style(&style); VARIANT v; v.vt=VT_BSTR; v.bstrVal=buf; if (style) style->put_backgroundColor(v); if (style) style->Release(); if (body) body->Release(); if (!body || !style) return E_FAIL; return NOERROR; } return NOERROR; } // IObjectWithSite... HRESULT STDMETHODCALLTYPE GetSite(REFIID riid, void** ppvSite) {return E_NOINTERFACE;} HRESULT STDMETHODCALLTYPE SetSite(IUnknown* iunk) { // This is called by IE to plug us into the current web window release(); iunk->QueryInterface(IID_IWebBrowser2, (void**)&webBrowser); IConnectionPointContainer *cpc=0; iunk->QueryInterface(IID_IConnectionPointContainer, (void**)&cpc); IConnectionPoint* cp=0; if (cpc) cpc->FindConnectionPoint(DIID_DWebBrowserEvents2, &cp); DWORD cookie; HRESULT hr; if (cp) hr=cp->Advise(static_cast<IDispatch*>(this), &cookie); if (!webBrowser || !cpc || !cp || hr!=S_OK) {if (cp) cp->Release(); if (cpc) cpc->Release(); release(); return E_FAIL;} return S_OK; } // BHO... BHO() : ref(0), webBrowser(0), doc(0), doc2(0), win2(0) {}; ~BHO() {release();} void release() {if (webBrowser) webBrowser->Release(); webBrowser=0; if (doc) doc->Release(); doc=0; if (doc2) doc2->Release(); doc2=0; if (win2) win2->Release(); win2=0;} }; class MyClassFactory : public IClassFactory { long ref; public: // IUnknown... (nb. this class is instantiated statically, which is why Release() doesn't delete it.) HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppv) {if (riid==IID_IUnknown || riid==IID_IClassFactory) {*ppv=this; AddRef(); return S_OK;} else return E_NOINTERFACE;} ULONG STDMETHODCALLTYPE AddRef() {InterlockedIncrement(&gref); return InterlockedIncrement(&ref);} ULONG STDMETHODCALLTYPE Release() {int tmp = InterlockedDecrement(&ref); InterlockedDecrement(&gref); return tmp;} // IClassFactory... HRESULT STDMETHODCALLTYPE LockServer(BOOL b) {if (b) InterlockedIncrement(&gref); else InterlockedDecrement(&gref); return S_OK;} HRESULT STDMETHODCALLTYPE CreateInstance(LPUNKNOWN pUnkOuter, REFIID riid, LPVOID *ppvObj) {*ppvObj = NULL; if (pUnkOuter) return CLASS_E_NOAGGREGATION; BHO *bho=new BHO(); bho->AddRef(); HRESULT hr=bho->QueryInterface(riid, ppvObj); bho->Release(); return hr;} // MyClassFactory... MyClassFactory() : ref(0) {} }; STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID *ppvOut) { static MyClassFactory factory; *ppvOut = NULL; if (rclsid==BhoCLSID) {return factory.QueryInterface(riid,ppvOut);} else return CLASS_E_CLASSNOTAVAILABLE; } STDAPI DllCanUnloadNow(void) { return (gref>0)?S_FALSE:S_OK; } STDAPI DllRegisterServer(void) { TCHAR fn[MAX_PATH]; GetModuleFileName(hInstance,fn,MAX_PATH); SHSetValue(HKEY_CLASSES_ROOT,_T("CLSID\\")BhoCLSIDs,_T(""),REG_SZ,_T("BHO"),4*sizeof(TCHAR)); SHSetValue(HKEY_CLASSES_ROOT,_T("CLSID\\")BhoCLSIDs _T("\\InProcServer32"),_T(""),REG_SZ,fn,((int)_tcslen(fn)+1)*sizeof(TCHAR)); SHSetValue(HKEY_CLASSES_ROOT,_T("CLSID\\")BhoCLSIDs _T("\\InProcServer32"),_T("ThreadingModel"),REG_SZ,_T("Apartment"),10*sizeof(TCHAR)); SHSetValue(HKEY_LOCAL_MACHINE,_T("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Browser Helper Objects\\")BhoCLSIDs,_T(""),REG_SZ,_T(""),sizeof(TCHAR)); return S_OK; } STDAPI DllUnregisterServer() { SHDeleteKey(HKEY_CLASSES_ROOT,_T("CLSID\\") BhoCLSIDs); SHDeleteKey(HKEY_LOCAL_MACHINE,_T("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Browser Helper Objects\\")BhoCLSIDs); return S_OK; } BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { if (fdwReason==DLL_PROCESS_ATTACH) hInstance=hinstDLL; return TRUE; }
Note: I've only intercepted a few of the possible events. Others you might consider intercepting include DISPID_BEFORENAVIGATE DISPID_NAVIGATECOMPLETE DISPID_STATUSTEXTCHANGE DISPID_QUIT DISPID_DOWNLOADCOMPLETE DISPID_COMMANDSTATECHANGE DISPID_DOWNLOADBEGIN DISPID_NEWWINDOW DISPID_PROGRESSCHANGE DISPID_WINDOWMOVE DISPID_WINDOWRESIZE DISPID_WINDOWACTIVATE DISPID_PROPERTYCHANGE DISPID_TITLECHANGE DISPID_FRAMEBEFORENAVIGATE DISPID_FRAMENAVIGATECOMPLETE DISPID_FRAMENEWWINDOW DISPID_BEFORENAVIGATE2 DISPID_NEWWINDOW2 DISPID_NAVIGATECOMPLETE2 DISPID_ONQUIT DISPID_ONVISIBLE DISPID_ONTOOLBAR DISPID_ONMENUBAR DISPID_ONSTATUSBAR DISPID_ONFULLSCREEN DISPID_ONTHEATERMODE DISPID_ONADDRESSBAR DISPID_RESETFIRSTBOOTMODE DISPID_RESETSAFEMODE DISPID_REFRESHOFFLINEDESKTOP DISPID_ADDFAVORITE DISPID_ADDCHANNEL DISPID_ADDDESKTOPCOMPONENT DISPID_ISSUBSCRIBED DISPID_SHELLUIHELPERLAST