If you want to extend IE with a browser helper object or a toolbar, you need to implement IObjectWithSite interface. ATL has a basic IObjectWithSiteImpl<> template that helps a bit.  Most BHOs and toolbars also subscribe to the browser events. ATL has a helper template for that as well - IDispEventImpl<>. Thus, pretty much any IE extension implemented with ATL inherits both templates. So, I've decided to implement a new template - IObjectWithBrowserSite<>, that besides storing the site pointer as IUnknown, also stores a pointer to IWebBrowser2 and subscribes to the browser events.

The new template declaration looks like this:

template <class T, UINT nID>
class ATL_NO_VTABLE IObjectWithBrowserSiteImpl :
   
public IObjectWithSiteImpl<T>,
   
public IDispEventImpl<nID, T, &DIID_DWebBrowserEvents2, &LIBID_SHDocVw, 1, 1>
{
public:
    // IObjectWithSite
   
IFACEMETHOD(SetSite)(IUnknown *pUnkSite)
    {
        HRESULT hr = S_OK;

        // Silently ignore any failure from CleanupSite
       
CleanupSite();

        // Store the IUnknown pointer to the new site
       
hr = IObjectWithSiteImpl<T>::SetSite(pUnkSite);

        if (SUCCEEDED(hr))
            hr = SetupSite();

        if (FAILED(hr))
           
// Our setup logic failed; the real failure that caused
           
// us to clean up here is more important, so propagate this one to the site
           
// Silently ignore any failure from CleanupSite;
           
CleanupSite();

        return hr;
    }

    // DWebBrowserEvents2
   
STDMETHOD(OnQuit)()
    {
       
return DetachBrowserEvents();
    }

protected:
    // Setup and cleanup on site change
   
STDMETHOD(SetupSite)()
    {
        HRESULT hr = S_OK;

        if (m_spUnkSite)
        {
            // We have a site; get the new browser pointer
           
m_pWebBrowser = m_spUnkSite;

            if (m_pWebBrowser == NULL)
            {
                hr = E_INVALIDARG;

                // We have a site, but it's not a browser; get the new browser pointer from the site's IServiceProvider
               
CComQIPtr<IServiceProvider> pServiceProvider = m_spUnkSite;

                if (pServiceProvider != NULL)
                    hr = pServiceProvider->QueryService(SID_SInternetExplorer, IID_IWebBrowser2, (
void**)&m_pWebBrowser);
            }

            if (m_pWebBrowser != NULL)
               
// Attach to the new browser events
               
hr = AttachBrowserEvents();
        }

        return hr;
    }

    STDMETHOD(CleanupSite)()
    {
       
if (m_pWebBrowser != NULL)
        {
           
// Detach from the current site events
           
HRESULT hr = DetachBrowserEvents();

            // IE7 disconnects our connection points _before_ calling us with SetSite(NULL)
            // so DispUnadvise() returns CONNECT_E_NOCONNECTION (0x80040200)
            if (hr == CONNECT_E_NOCONNECTION)
                hr = S_OK;
 

            // Release the pointer to the current hosting web browser
           
m_pWebBrowser.Release();
        }

        // We are done with the current site
       
return IObjectWithSiteImpl<T>::SetSite(NULL);
    }

    // Browser event connection methods
   
STDMETHOD(AttachBrowserEvents)()
    {
       
// ATL's event cookie is 0xFEFEFEFE, when the sink is not advised
       
if (m_dwEventCookie != 0xFEFEFEFE)
           
return S_OK;

        return DispEventAdvise(m_pWebBrowser, &DIID_DWebBrowserEvents2);
    }

    STDMETHOD(DetachBrowserEvents)()
    {
       
// ATL's event cookie is 0xFEFEFEFE, when the sink is not advised
       
if (m_dwEventCookie == 0xFEFEFEFE)
           
return S_OK;

        return DispEventUnadvise(m_pWebBrowser, &DIID_DWebBrowserEvents2);
    }

    // Hosting browser
   
CComQIPtr<IWebBrowser2> m_pWebBrowser;
}

Here's how you would use that template as a base of your CBho class. The magic numebr 1 in the template instantiation below is the ID of the browser control, by which we will reference its events in the sink map. If your class subscribes to events from other objects, like ActiveX controls, you need to make sure the browser control ID is unique an d does not collide with another one.

class CBho;

#define IDC_BROWSER 1

typedef IObjectWithBrowserSiteImpl<CBho, IDC_BROWSER> CBhoBaseBrowserSiteImpl;

class ATL_NO_VTABLE CBho :
   
public CComObjectRootEx<CComSingleThreadModel>,
   
public CComCoClass<CBho, &CLSID_Bho>,
   
public CBhoBaseBrowserSiteImpl

The typedef of CBhoBaseBrowserSiteImpl is not really necessary, but it does make the your code more readable when you need to call the methods of IObjectWithBrowserSiteImpl<>.

There are couple more things you need to do to get this working. First, you need to add IObjectWithSite to your interface map, so IUnknown::QueryInterface() implementation will return proper pointer. Second, you need to add sink map for your events. Unfortunately, ATL sink macros don't support chaining, so the whole DWebBrowserEvents2 sink map needs to be in your class. This means that while the IObjectWithBrowserSiteImpl<> template defines standard OnQuit() handler, it's not hooked by default to the DWebBrowserEvents2::OnQuit event. You need to do that yourself in order to get yourself detached from the browser events. (Note: Although IE7 will take care of that, your code should not rely on that and should behave well; plus you might be hosted in IE6 or another host) There are two options - you can hook your own method and call OnQuit() explicitly, or you can hook OnQuit() and optionally override it (not forgetting to call the base implementation yourself :-)).

Here are the bare minimum sink and interface maps you need to have. Note the magin number 1 in the

BEGIN_SINK_MAP(CBho)
    SINK_ENTRY_EX(IDC_BROWSER, DIID_DWebBrowserEvents2, DISPID_ONQUIT, OnQuit)
END_SINK_MAP()

BEGIN_COM_MAP(CBho)
    COM_INTERFACE_ENTRY(IObjectWithSite)
END_COM_MAP()

You don't need to implement SetSite() itself; you can see that I've split it into two separate methods - SetupSite() and CleanupSite() which you can override. Of course, you still have the choice to override the provided SetSite implementation and do whatever you need. I find this structure somewhat cleaner as the setup and the cleanup logic are strictly separated; plus, now on setup failure the code does automatic cleanup.

If you implement an IE toolbar instead of a BHO, the pointer you get on SetSite() is not a pointer to the browser control. You can still use the above template as a base IObjectWithSite class, though. The SetupSite() implementation will take care of getting the browser pointer through the toolbar site's IServiceProvider interface.